perm filename LRNSAM.DGL[UP,DOC]2 blob
sn#288560 filedate 1977-06-15 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00113 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00018 00002
C00020 00003
C00022 00004 LRNSAM DRAFT
C00026 00005 LRNSAM Table of Contents DRAFT
C00031 00006 LRNSAM Table of Contents DRAFT
C00037 00007 LRNSAM Table of Contents DRAFT
C00041 00008 LRNSAM DRAFT
C00045 00009 LRNSAM DRAFT
C00050 00010 LRNSAM DRAFT
C00055 00011 LRNSAM Processing ticks and update ticks DRAFT
C00060 00012 LRNSAM DRAFT
C00064 00013 LRNSAM Tick time requirements DRAFT
C00067 00014 LRNSAM DRAFT
C00072 00015 LRNSAM Processing speed vs. capacity DRAFT
C00077 00016 LRNSAM Processing speed vs. capacity DRAFT
C00080 00017 LRNSAM DRAFT
C00084 00018 LRNSAM The processing elements DRAFT
C00086 00019 LRNSAM DRAFT
C00087 00020 LRNSAM DRAFT
C00091 00021 LRNSAM The flow of data DRAFT
C00095 00022 LRNSAM The flow of data DRAFT
C00097 00023 LRNSAM DRAFT
C00102 00024 LRNSAM Sum memory DRAFT
C00107 00025 LRNSAM Sum memory DRAFT
C00109 00026 LRNSAM DRAFT
C00114 00027 LRNSAM Generators DRAFT
C00118 00028 LRNSAM Generators DRAFT
C00121 00029 LRNSAM DRAFT
C00125 00030 LRNSAM Generator run modes DRAFT
C00130 00031 LRNSAM Generator run modes DRAFT
C00135 00032 LRNSAM Generator run modes DRAFT
C00136 00033 LRNSAM DRAFT
C00137 00034 LRNSAM DRAFT
C00143 00035 LRNSAM Oscillator processing DRAFT
C00145 00036 LRNSAM DRAFT
C00149 00037 LRNSAM Oscillator modes DRAFT
C00153 00038 LRNSAM Oscillator modes DRAFT
C00156 00039 LRNSAM DRAFT
C00161 00040 LRNSAM Envelope processing DRAFT
C00164 00041 LRNSAM DRAFT
C00168 00042 LRNSAM Envelope modes DRAFT
C00170 00043 LRNSAM DRAFT
C00174 00044 LRNSAM Shape-shifting generators DRAFT
C00179 00045 LRNSAM Shape-shifting generators DRAFT
C00183 00046 LRNSAM DRAFT
C00188 00047 LRNSAM Magic numbers DRAFT
C00194 00048 LRNSAM Magic numbers DRAFT
C00197 00049 LRNSAM DRAFT
C00201 00050 LRNSAM Modifiers DRAFT
C00206 00051 LRNSAM Modifiers DRAFT
C00208 00052 LRNSAM DRAFT
C00212 00053 LRNSAM Modifier procedures DRAFT
C00216 00054 LRNSAM Modifier procedures DRAFT
C00219 00055 LRNSAM Modifier procedures DRAFT
C00223 00056 LRNSAM Modifier procedures DRAFT
C00228 00057 LRNSAM Modifier procedures DRAFT
C00232 00058 LRNSAM Modifier procedures DRAFT
C00233 00059 LRNSAM DRAFT
C00236 00060 LRNSAM Filtering algorithms DRAFT
C00238 00061 LRNSAM Filtering algorithms DRAFT
C00242 00062 LRNSAM Filtering algorithms DRAFT
C00247 00063 LRNSAM Filtering algorithms DRAFT
C00252 00064 LRNSAM Filtering algorithms DRAFT
C00253 00065 LRNSAM DRAFT
C00257 00066 LRNSAM Delay Units DRAFT
C00261 00067 LRNSAM Delay Units DRAFT
C00263 00068 LRNSAM DRAFT
C00267 00069 LRNSAM Calling the box DRAFT
C00271 00070 LRNSAM Calling the box DRAFT
C00276 00071 LRNSAM Calling the box DRAFT
C00279 00072 LRNSAM Calling the box DRAFT
C00282 00073 LRNSAM Calling the box DRAFT
C00284 00074 LRNSAM Calling the box DRAFT
C00288 00075 LRNSAM Calling the box DRAFT
C00291 00076 LRNSAM Calling the box DRAFT
C00294 00077 LRNSAM Calling the box DRAFT
C00297 00078 LRNSAM Calling the box DRAFT
C00300 00079 LRNSAM DRAFT
C00305 00080 LRNSAM An introduction to pipelining DRAFT
C00311 00081 LRNSAM DRAFT
C00315 00082 LRNSAM Time division multiplexing DRAFT
C00319 00083 LRNSAM DRAFT
C00323 00084 LRNSAM Basic number theory DRAFT
C00327 00085 LRNSAM Basic number theory DRAFT
C00330 00086 LRNSAM Basic number theory DRAFT
C00333 00087 LRNSAM DRAFT
C00337 00088 LRNSAM SAIL examples DRAFT
C00341 00089 LRNSAM SAIL examples DRAFT
C00345 00090 LRNSAM SAIL examples DRAFT
C00349 00091 LRNSAM SAIL examples DRAFT
C00353 00092 LRNSAM SAIL examples DRAFT
C00357 00093 LRNSAM SAIL examples DRAFT
C00361 00094 LRNSAM SAIL examples DRAFT
C00365 00095 LRNSAM SAIL examples DRAFT
C00369 00096 LRNSAM SAIL examples DRAFT
C00373 00097 LRNSAM SAIL examples DRAFT
C00376 00098 LRNSAM SAIL examples DRAFT
C00380 00099 LRNSAM SAIL examples DRAFT
C00382 00100 LRNSAM DRAFT
C00386 00101 LRNSAM Generator field and mode tables DRAFT
C00388 00102 LRNSAM DRAFT
C00391 00103 LRNSAM Modifier field and mode tables DRAFT
C00394 00104 LRNSAM DRAFT
C00397 00105 LRNSAM DRAFT
C00401 00106 LRNSAM Reserved Words DRAFT
C00405 00107 LRNSAM Reserved Words DRAFT
C00409 00108 LRNSAM Reserved Words DRAFT
C00412 00109 LRNSAM DRAFT
C00414 00110 LRNSAM Index DRAFT
C00420 00111 LRNSAM Index DRAFT
C00426 00112 LRNSAM Index DRAFT
C00432 00113
C00433 ENDMK
C⊗;
Systems Concepts
Digital Synthesizer
operations manual
and tutorial
by Gareth Loy
Center for Computer Research in Music and Acoustics
Department of Music
Stanford University
Stanford, California 94305
This work was supported in part by a grant from the National Endowment for the
Arts and by NSF contract DCR 75-00694.
Abstract
This document describes the low level operation of Pete Samson's Systems
Concepts Digital Synthesizer, beginning with a general discussion of the
internal and external data structures, then the workings of the processing
elements, finally, a discussion of the SAIL procedures which form the lowest
level of user control. At the end are appendices containing introductions to
pipelining and time-division multiplexing, and examples of sample calls using
the SAIL procedures.
Acknowledgments
The existence of this document is entirely due to the patience, teaching and
editing I have received over the course of its writing from Andy Moorer and Mark
Kahrs.
LRNSAM DRAFT
T A B L E O F C O N T E N T S
SECTION PAGE
Section 1 Introduction
1-1 The nature of the problem . . . . . . . . . . 1
1-2 Reading this manual . . . . . . . . . . . . 1
Section 2 Definitions
Section 3 Processing ticks and update ticks
Section 4 Tick time requirements
4-1 Generator tick time . . . . . . . . . . . . 5
4-2 Modifier tick time . . . . . . . . . . . . 5
4-3 Combined tick times . . . . . . . . . . . . 5
4-4 General formula . . . . . . . . . . . . . 6
Section 5 Processing speed vs. capacity
5-1 Speed of processing . . . . . . . . . . . . 7
5-2 Ticks per pass . . . . . . . . . . . . . . 7
5-3 Processing vs. update ticks . . . . . . . . . 8
5-4 Budgeting ticks . . . . . . . . . . . . . 8
5-5 Numbering Processing elements . . . . . . . . . 9
Section 6 The processing elements
6-1 Generators . . . . . . . . . . . . . . . 10
6-2 Modifiers . . . . . . . . . . . . . . . 10
6-3 Sum memory . . . . . . . . . . . . . . . 11
6-4 Delay memory . . . . . . . . . . . . . . 11
Section 7 The flow of data
7-1 The Connection... . . . . . . . . . . . . . 13
7-2 Command path . . . . . . . . . . . . . . 13
7-3 I/O path . . . . . . . . . . . . . . . . 14
7-4 Read path and Write path . . . . . . . . . . 14
7-5 DAC path . . . . . . . . . . . . . . . . 14
7-6 It's a Stream machine... . . . . . . . . . . 14
Section 8 Sum memory
8-1 This pass - last pass division . . . . . . . . 16
8-2 Generator - modifier division . . . . . . . . . 17
8-3 Addressing sum memory . . . . . . . . . . . 17
Page i
LRNSAM Table of Contents DRAFT
Section 9 Generators
9-1 Generator parameters . . . . . . . . . . . . 20
Section 10 Generator run modes
10-1 INACTIVE . . . . . . . . . . . . . . . . 22
10-2 G_PAUSE . . . . . . . . . . . . . . . . 22
10-3 G_WAIT . . . . . . . . . . . . . . . . 23
10-4 x_RUNNING . . . . . . . . . . . . . . . 23
10-5 DAC_WRITE . . . . . . . . . . . . . . . 24
10-6 READ_DATA . . . . . . . . . . . . . . . 24
10-7 WRITE_DATA . . . . . . . . . . . . . . . 24
Section 11 Oscillator processing
Section 12 Oscillator modes
12-1 COSINE . . . . . . . . . . . . . . . . 29
12-2 SAWTOOTH . . . . . . . . . . . . . . . . 29
12-3 SQUARE . . . . . . . . . . . . . . . . 30
12-4 PULSE_TRAIN . . . . . . . . . . . . . . . 30
12-5 COS_FM . . . . . . . . . . . . . . . . 30
12-6 SUM_OF_COSINES . . . . . . . . . . . . . . 30
12-7 A note on band limiting . . . . . . . . . . . 31
Section 13 Envelope processing
13-1 EXPONENT and RATE . . . . . . . . . . . . . 32
13-2 ASYMPTOTE . . . . . . . . . . . . . . . 32
Section 14 Envelope modes
Section 15 Shape-shifting generators
15-1 RATE . . . . . . . . . . . . . . . . . 36
15-2 FREQUENCY . . . . . . . . . . . . . . . 37
15-3 ANGLE . . . . . . . . . . . . . . . . . 37
15-4 NCOSINES and SCALE . . . . . . . . . . . . 37
15-5 FM . . . . . . . . . . . . . . . . . . 38
15-6 SUM_MEMORY . . . . . . . . . . . . . . . 38
15-7 RATE EXPONENT and ASYMPTOTE . . . . . . . . . 38
Section 16 Magic numbers
Section 17 Modifiers
17-1 Modifier parameters . . . . . . . . . . . . 42
17-2 Running terms . . . . . . . . . . . . . . 42
17-3 Coefficient terms and Read terms . . . . . . . . 43
17-4 Scaling terms . . . . . . . . . . . . . . 43
17-5 Mode . . . . . . . . . . . . . . . . . 43
17-6 Sum memory . . . . . . . . . . . . . . . 44
17-7 Initializing . . . . . . . . . . . . . . 44
Page ii
LRNSAM Table of Contents DRAFT
Section 18 Modifier procedures
18-1 Inactive . . . . . . . . . . . . . . . . 45
18-2 Mixing . . . . . . . . . . . . . . . . 45
18-3 Integer mixing . . . . . . . . . . . . . . 46
18-4 Latch . . . . . . . . . . . . . . . . . 46
18-5 Signum . . . . . . . . . . . . . . . . 47
18-6 Zero-crossing pulser . . . . . . . . . . . . 47
18-7 Minimum . . . . . . . . . . . . . . . . 47
18-8 Maximum . . . . . . . . . . . . . . . . 47
18-9 Amplitude modulation . . . . . . . . . . . . 48
18-10 Four-quadrant-multiply . . . . . . . . . . . 48
18-11 Uniform noise . . . . . . . . . . . . . . 48
18-12 Triggered uniform noise . . . . . . . . . . . 49
18-13 Threshold . . . . . . . . . . . . . . . 50
18-14 Invoke delay unit . . . . . . . . . . . . . 50
Section 19 Filtering algorithms
19-1 One pole . . . . . . . . . . . . . . . . 52
19-2 One zero . . . . . . . . . . . . . . . . 52
19-3 Two poles . . . . . . . . . . . . . . . 53
19-4 Two zeros . . . . . . . . . . . . . . . 53
19-5 Two poles COEFF0 variable . . . . . . . . . . 53
19-6 Two poles COEFF1 variable . . . . . . . . . . 53
19-7 Two zeros COEFF0 variable . . . . . . . . . . 54
19-8 Two zeros COEFF1 variable . . . . . . . . . . 54
19-9 A little digital filtering theory . . . . . . . 54
Section 20 Delay Units
20-1 Delay line mode . . . . . . . . . . . . . 61
20-2 Table lookup mode . . . . . . . . . . . . . 62
20-3 Table lookup - rounded . . . . . . . . . . . 62
Section 21 Calling the box
21-1 GET . . . . . . . . . . . . . . . . . 64
21-2 GIVE . . . . . . . . . . . . . . . . . 65
21-3 BIND . . . . . . . . . . . . . . . . . 65
21-4 SET_OUTPUT . . . . . . . . . . . . . . . 66
21-5 SET_CHANNEL . . . . . . . . . . . . . . . 66
21-6 SET_PROCEDURE . . . . . . . . . . . . . . 67
21-7 DECODE . . . . . . . . . . . . . . . . 67
21-8 RELATIVE . . . . . . . . . . . . . . . . 68
21-9 SET_MODE . . . . . . . . . . . . . . . . 68
21-10 SET_FIELD . . . . . . . . . . . . . . . 69
21-11 BIND_FIELD . . . . . . . . . . . . . . . 70
21-12 Size of command buffer . . . . . . . . . . . 71
21-13 LOAD_DELAY . . . . . . . . . . . . . . . 71
21-14 INITIALIZE . . . . . . . . . . . . . . . 71
21-15 FLUSH . . . . . . . . . . . . . . . . . 72
Page iii
LRNSAM Table of Contents DRAFT
21-16 Processing element arrays . . . . . . . . . . 72
21-17 Tick counters . . . . . . . . . . . . . . 73
21-18 Steps in calling the box . . . . . . . . . . 73
Appendix 1 An introduction to pipelining . . . . . . . . . . . . 75
Appendix 2 Time division multiplexing . . . . . . . . . . . . . 77
Appendix 3 Basic number theory . . . . . . . . . . . . . . . 79
Appendix 4 SAIL examples . . . . . . . . . . . . . . . . . 83
4-1 Generator example . . . . . . . . . . . . . 83
4-2 Modifier example . . . . . . . . . . . . . 87
4-3 Delay line example . . . . . . . . . . . . 93
4-4 Writing your own procedures . . . . . . . . . 94
Appendix 5 Generator field and mode tables . . . . . . . . . . . 96
5-1 Generator parameters . . . . . . . . . . . . 96
5-2 Generator run modes . . . . . . . . . . . . 96
5-3 Oscillator modes . . . . . . . . . . . . . 97
5-4 Envelope modes . . . . . . . . . . . . . . 97
Appendix 6 Modifier field and mode tables . . . . . . . . . . . 98
6-1 Modifier parameters . . . . . . . . . . . . 98
6-2 Modifier procedures . . . . . . . . . . . . 99
Appendix 7 Delay unit field and mode tables . . . . . . . . . . . 100
7-1 Delay units: timing . . . . . . . . . . . . 100
Appendix 8 Reserved Words . . . . . . . . . . . . . . . . . 101
Appendix 9 Error messages . . . . . . . . . . . . . . . . . 105
Page iv
LRNSAM DRAFT
Section 1
_______ _
Introduction
____________
1-1. The nature of the problem
The Samson box creates sounds by producing streams of binary numbers, called
samples, at a blinding rate of speed. The process includes digital to analog
conversion which changes these samples into analog voltages that are eventually
fed to a sound system. There is a need for extreme speed in calculating the
samples which can be appreciated if it is realized that according to the Nyquist
Sampling Theorem, one cannot represent a frequency higher than 1/2 of the rate
at which the samples are produced (called the sampling rate). This means that
for reasonably good hi-fi sound, where the frequency response might go up to 15
kHz or beyond, the machine must be capable of at least a 30 kHz sampling rate
per channel of sound. This can be easy or difficult to do depending on how much
computation goes into each sample. But typically, each sample is made up of
great amounts of computation such that the speed limits of most modern computers
can be quickly surpassed, especially considering that production of interesting
music by computers typically requires a great deal of sophistication in the
models used to generate the sounds. So we see that we must be clever in how we
structure the computations in order to get a good fast sampling rate but still
to have enough time left over to compute each sample. The Samson box is a study
in cleverness.
1-2. Reading this manual
This manual starts out assuming an elementary knowlege of the basic computer
science concepts of pipelining and time division multiplexing. If these terms
are unfamiliar to you you may want to first read appendicies 1 and 2 which are
designed to impart an understanding of these things to the (not quite total)
novice user. If you would like to review basic digital number theory, read
appendix 3.
The later sections on calling the Samson box from a SAIL environment and the
subsequent program examples are designed to be read by those already familiar
with the SAIL language. Generally, if you are only interested in the basic
processing of the box read until section 21.
Page 1
LRNSAM DRAFT
Section 2
_______ _
Definitions
___________
Before proceeding let's define some terms. These definitions are elaborated in
later sections of this manual.
The Samson box has two basic active parts, or processing elements. The first
element is called a generator, (see section 9) the second is a modifier (see
section 17). There are 256 generators and 128 modifiers.
The processing elements communicate with eachother through a scratchpad read-
alter memory called sum memory which is used to store the output from, and input
to the processing elements. There are 256 locations in sum memory. See section
8.
Delay units are a special kind of processing element. They must work in
conjunction with modifiers. They can reference up to 65k of 20 bit words of
memory (of which Stanford has 48k words). It is called delay memory. This is
for reverberation and general table lookups. See section 20.
The amount of time that it takes for the box to produce one cycle of output from
all the running modifiers and generators is called a pass. A pass is made up of
a series of ticks. A tick is the smallest counting unit in the box. It is the
amount of time necessary for the slowest micro-process to complete its
operation. There are two types of ticks.
Processing ticks count the computations in time of the processing elements.
Update ticks count the number of new values passed to the box from the outside
world. Both kinds of ticks take the same amount of time. In other words, when
sound samples are being computed we are counting in processing ticks. When we
are updating the box with new parameters supplied by the outside world, we are
using update ticks. The sum of all processing ticks and all update ticks equals
a pass. See section 3.
A pass is not to be confused with a sample. A sample is the result of an
individual sound pressure wave calculation. The number of samples produced in a
pass equals the number of channels outputing sounds. If the box is producing
one channel of music, then there will be one sample of sound produced per pass.
There are 16 possible DAC (digital to analog converter) addresses, of which
Stanford has eight.
Page 2
LRNSAM DRAFT
Section 3
_______ _
Processing ticks and update ticks
__________ _____ ___ ______ _____
Now let's take an example that will show how the two kinds of ticks interact.
Since a pass is the sum of processing and update ticks, we must first anticipate
the maximum number of processing element parameters we will want to change per
pass. This is the number of update ticks required. It takes one tick-time to
update one parameter in a processing element. Then we figure out how many
processing ticks it will take to allow all the procesing elements we want to
run. Each running processing element takes 9 tick-times. The sum of these
numbers is the total time in ticks the box will take to complete one pass. We
foreward the total number of ticks thus calculated to the box on a command and
the length of a pass is set accordingly. When the box runs, it first executes
all the processing ticks, then the update ticks are run. So when the box is
cranked up, it first enters the processing mode and it will act on the existing
numbers in the processing elements, then it enters update mode and will allow
one variable inside one processing element to be changed on each succeeding tick
until the end of the pass.
Please note the that update ticks have the lowest priority. If we end up
needing to update more parameters on one pass than we have update tick-time
available then the remainder will not happen on this pass, but will be snarfed
according to how many update ticks there are and you'll probably not get an
error message. If it affects the sound, as it undoubtedly will, it is called
signal degradation. That may or may not be reasonable depending on what you're
doing. The obvious thing to do is to make a compromise between the minimum
necessary update ticks and the longest acceptable pass period to produce an
acceptable sampling rate such as will allow plenty of time for all the update
ticks you can estimate needing.
Lets take as an example a generator that has two user-settable locations. One
location contains the number that represents the frequency. The other location
contains a number which changes the number in the frequency location by adding
its value to the number in the frequency location on every pass, thereby raising
or lowering the frequency uniformly by some amount or "slewing" it, producing a
glissando. We will diagram it this way:
Page 3
LRNSAM Processing ticks and update ticks DRAFT
SLEW
↓
+ ←←←←←←
↓ ↑
FREQUENCY ↑
↓ ↑
↓ →→→→→→
↓
GENERATOR
↓
samples
Fig. 1
____ _
On every pass the number in FREQUENCY is sent to the GENERATOR which produces
the sample passed as the output. But the FREQUENCY number is also piped around
and added into the number coming down from SLEW. The sum of FREQUENCY and SLEW
replaces the old value in FREQUENCY. But SLEW doesn't change, it's a constant.
You can see that this thing can run forever by itself. On each pass a new
frequency is formed and a new sample is shipped out.
Since we'd like to be able to change its values from time to time, lets declare
that there will be two ticks per pass, one processing tick, and one update tick.
As we begin looking at the processing stream, we notice it's already been
running and that the last output was 1000 and SLEW had 1 in it. If this went on
as is, we would send succeeding values of 1001,1002,1003... etc. to the
GENERATOR. But let's say that we're tired of this and want to change it, so
we'll put 3 in SLEW at our first chance (where the first vertical arrow is), and
we'll change FREQUENCY to 500 at our next chance (at the second arrow). The two
rows SLEW and FREQ. in the next figure show the progression of values of the two
settable locations in our generator. The bottom line shows what frequency the
generator is actually producing on that pass.
|<--- pass -->|<-- pass --->|<--- pass -->|<--- pass -->|
| | ↓ | ↓ | |
|proc| |upd.| |proc| |UPD.| |proc| |UPD.| |proc| |upd.| |
|____|_|____|_|____|_|____|_|____|_|____|_|____|_|____| |
Slew | 1| | 1| | 1| | 3| | 3| | 3| | 3| | 3| |
|____|_|____|_|____|_|____|_|____|_|____|_|____|_|____| |
Freq. |1001| |1001| |1002| |1002| |1005| | 500| | 503| | 503| |
|____|_|____|_|____|_|____|_|____|_|____|_|____|_|____| |
Out |1000| |1001| |1002| |1005| |503|...
Fig. 2
____ _
Page 4
LRNSAM DRAFT
Section 4
_______ _
Tick time requirements
____ ____ ____________
4-1. Generator tick time
Each running generator requires one processing tick; the length of the generator
pipeline is 9 ticks.
So if we had, say, 11 generators running, and assumed for the moment that there
were no update ticks, how many ticks would it take before we got a complete set
of valid results for all the generators? That is to say, when would the first
pass end? It would end after 20 ticks, right? If not right, go back and reread
the part about pipelining again in appendix 1. When would the next pass and
all subsequent passes end? Right,... after 11 more ticks. So the formula for
processing ticks required by each running generator is:
<# of running generators> + 9. Equ. 1
4-2. Modifier tick time
Modifiers do general numerical and logic processing. As the name implies, they
modify existing numbers. In fact, they are supplied with two inputs and one
output (See section 17). Since modifiers have to do two memory references to
get input data from sum memory where generators do but one (see section 9) they
require two processing ticks. However, the length of the modifier pipeline is
also nine ticks. So say we had 7 running modifiers with no update ticks, how
long until the first complete set of valid numbers comes out? The answer is 23.
So, the tick requirement for modifiers is:
2*<# of running modifiers> + 9. Equ. 2
4-3. Combined tick times
Now let's take an example where we have both modifiers and generators running
simultaneously (still without update ticks). We must first know that there are
really two processors,one of which represents the modifiers and the other the
generators. These are seperate nests of electronics that run essentially
independently of eachother (although they are actually in lock step).
Page 5
LRNSAM Tick time requirements DRAFT
How many processing ticks for the first pass would be required if we have, say,
11 running generators and 5 running modifiers? The correct answer is 20 ticks.
The tick requirement for the first complete set of modifier output is 19, but
that for generators is 20. Since they are parallel processes, the modifiers can
do their whole thing in 19 ticks while the generators are also running.
4-4. General formula
So the official formula reads as follows, and I quote: "The number of processing
ticks is nine more than the maximum of: the number of generators used; twice the
number of modifiers used."(1) Or,
total proceessing ticks =
2*<# modifiers used> MAX <# generators used> + 9. Equ. 3
The Delay units have their own formula for tick requirements which is not shown
here. See Appendix 7.
One last word about processing and update ticks. References to sum memory and
delay memory are done on processing ticks, not update ticks. Update ticks only
refer to data coming in from outside the Samson box.
_______________________
(1) Systems Concepts Digital Synthesizer Programming Specification .
_______ ________ _______ ___________ ___________ _____________ _
Page 6
LRNSAM DRAFT
Section 5
_______ _
Processing speed vs. capacity
__________ _____ ___ ________
5-1. Speed of processing
The number of ticks per pass is set by the programmer. This is so because there
is a playoff between passes and the two kinds of ticks. The nut of the problem
is that, when all is said and done, the basic speed of processing one tick is
-7
195 nsec. (That's .0000000195 sec., or 1.95 x 10 , or 195 billionths of a
second.) That's the time that it takes a processing element to do the slowest
thing it has to do. So the whole box is geared to go at this rate by an
internal clock. This means that the maximum number of ticks we can have in one
second is 5,128,205.128.
Now, as you recall, the whole purpose of the box is to produce bunches of
numbers which are passed at blinding speeds to the DAC. Each number sent to the
DAC represents a complex calculation of the value of whatever waveform the box
is producing. Taken through time, the individual samples sent to the DAC
produce the desired waveform. The samples must be passed to the DAC at a
uniform rate, and at a sufficiently high speed so as to get a good high
frequency response. For instance, a typical sampling rate of 12800 samples per
second produces a usable frequency response from 0 to 6400 Hz. Faster rates
produce higher frequency responses. So naturally we want a good fat sampling
rate. But we've got to watch out for a catch, namely, that the box can only do
so much so fast. The barrier is the 195 nsec. tick turn around time.
5-2. Ticks per pass
The problem should become clear when we try to feed samples to the DAC in real
time. Real time, in this example means that we will attempt to guarantee that
Sam will send 12800 samples to the DAC per second. That means one pass takes
-5
7.8125 x 10 seconds. Furthermore, Sam must send them at an even rate, such
that each pass gets computed and each sample shipped out in 1/12800'th of a
second. How many ticks can we squeeze out of this much time? That comes to
-5 -7
7.8125 x 10 / 1.95 x 10 = 400 ticks per pass. This then is the maximum
Page 7
LRNSAM Processing speed vs. capacity DRAFT
number of ticks of either kind that can be squeezed into a pass. That's a
pretty good number, actually. That means we could have all the generators
running using up 256 ticks and still have 144 left as update ticks. But if we
were really greedy, and wanted even more ticks than this at this sampling rate,
we would no longer be able to guarantee a sample on each pass and we would
sooner or later (probabily sooner) fall behind. This is called data underrun
and when this happens, we drop out of real time. Another way to say it is: you
drop out of real time if the duration of the pass period is larger than the
sample period.
This is called processing degradation. It's not altogether catastrophic in that
there are several ways to suffer this gracefully if it becomes necessary, such
as to 1) lower the sampling rate thereby increasing the sampling period and
hence the pass period, 2) lower the complexity of the process thereby reducing
the number of ticks required and shrinking the pass period, or 3) to accept less
than real time processing and pass the samples to some form of memory (probabily
the disk) from whence they can be played as a whole when the processing is done.
(Even here there are limitations in that the disk bandwidth, i.e. the rate of
data transfer, becomes your limitation. Disk I/O is currently the number one
bottleneck in the A.I. system.)
5-3. Processing vs. update ticks
Not only is there a playoff between the desire to have the sampling rate as fast
as possible and the desire to have moby amounts of ticks per pass, but there is
a playoff between the two kinds of ticks, to wit: Processing ticks and update
ticks occupy mutually conflicting time domains. All the processing ticks are
performed first, then what's left is given to the update ticks. So if your
piece requires lots of real time user interaction with keyboards or what have
you, or if you are repatching the connections and modes of the processing
elements all the time, then you will be forced to allocate a larger part of each
pass to sending update information to the box, and that means lots of update
ticks per pass. And since no processing can be done in a processing element
while it is having its parameters updated, this reduces the number of processing
ticks available on any given sample, and means that the instrument must be
simpler than one which had fewer update ticks. The obverse works as well: too
complex an instrument means you have fewer update ticks per sample. So, as old
Richard Almaniac used to say, "budget your ticks wisely!"
5-4. Budgeting ticks
Now when I said above that the user sets the number of ticks, what that really
comes down to is that the user sets two numbers, the total number of ticks per
Page 8
LRNSAM Processing speed vs. capacity DRAFT
pass, and the total number of those ticks which are processing ticks. The
remainder are, of course, update ticks. Now in figuring this number, we will
ignore the nine ticks tacked on to represent the length of the processing
element pipeline. So we really only need to add up the number of generators
used, take the maximum of this number or twice the number of modifiers, plus the
number of update ticks we expect to need. This will get the number that we will
actually send off to the box to be the total number of ticks per pass. Sam will
take this number and compute the sampling rate that it equals, and set up
everything accordingly. Actually, the number arrived at in this way may or may
not correspond to an existing low pass filter implemented in the box. In that
case, you get the filter whose cutoff frequency is less than or equal to 1/2 the
sampling rate calculated as above.
5-5. Numbering Processing elements
One final word of note: the processing elements are all numbered ordinally.
Generators go from 0 to 255 modifiers go from 0 to 127. Processing always begins
with the lowest numbered running processing element and goes sequentially until
the end of the pass. Since speed is typically such an important commodity here,
it is important to always use the lowest numbered consecutive block of
processing elements as possible. If you have a processing element in there that
is really not being used but its number is below the highest one in use, you are
wasting compute time (sinful!).
Page 9
LRNSAM DRAFT
Section 6
_______ _
The processing elements
___ __________ ________
As you have seen, Sam has two basic types of processing elements: generators and
modifiers. There is an array of memory locations called sum memory in the box
which these processing elements use to pass numbers among themselves. Also
contained within Sam is a large memory called delay memory. This memory is
accessed by the modifiers and can be used as delay lines (hence the name) for
realizing filters and reverberators. The delay memory can also function as
table lookup memory allowing various computer-loaded functions and tables to be
easily accommodated.
6-1. Generators
The generators peform two functions: waveform generation and dynamic amplitude
control of the generated waveform. The portion that performs the waveform
generation is called the oscillator, and the portion that handles the amplitude
control we'll call the envelope controller. Generators can produce sine
(cosine), square and saw-tooth waveforms, pulse train, and equal-amplitude sum-
of-cosines (band limited pulse trains). They can be frequency modulated by the
output of any other processing element, and the frequency can also be swept
linearly. The envelope side has facility to shape amplitude using either linear
or exponential curves. Sam has 256 generators, and they can all be running
simultaneously. Which of these various operations the generator does is
dependent on the generator's mode, which is set during update tick time.
6-2. Modifiers
Modifiers can act as filters, amplitude modulators, four-quadrant multipliers
(signed multiply), sample mixers and uniform noise generators (white noise and
random number generation). They can also do signal latching (sample and hold),
sign testing, signal zero-crossing detection, and minimum or maximum testing.
Modifiers are also used to pass data to and from the delay memory for
reverberation or table lookup. Sam contains 128 modifiers all of which can be
running at once. The mode is likewise (and for all processing elements) set on
update tick time.
Page 10
LRNSAM The processing elements DRAFT
6-3. Sum memory
Information is passed among generators and modifiers through what is called sum
memory. This can be thought of as a kind of telephone switch-board because it
is through the sum memory that the data put out by one processing element is
accessed by other processing elements. Sum memory is accessed during processing
tick time.
6-4. Delay memory
Delay memory has two uses: It can simulate delay lines for reverberation and
filtering, and it can store precomputed tables, such as time-domain waveforms or
mathematical functions that would not be possible (on our budget) to have built
in. A port into delay memory consists of a modifier in DELAY_LINE mode (see
"section!" MODIFIERS} for modifier modes) and a delay unit. There are 32 delay
units, and they can all be running at once.
Page 11
LRNSAM DRAFT
BLKDIA.XIP[DOC,MUS] goes here.
Page 12
LRNSAM DRAFT
Section 7
_______ _
The flow of data
___ ____ __ ____
7-1. The Connection...
The interface of the Samson box to the world at large is roughly this: we, the
users, speak to the PDP-10 which runs the AI timesharing system and the PDP-10
speaks to the Samson box. It's not that simple, there's another computer
between the 10 and the box, but since it is transparent to the overall process,
we'll leave it out here. Also connected to the Samson box are the DACs that get
the samples the box produces.
The following is a list of the different kinds of things we would like to be
able to pass between these devices: we will need to be able to 1) send commands
from the general timesharing system, 2) read back diagnostic information, 3)
read and write sound samples from and to the system, and finally, 4) to write
samples to DACs. But before we go into greater detail, a caveat must be made
about the discussion of the first three items. What is presented is a
description of a "typical" usage of the Samson box rather than a description of
what actually is being done at SAIL because the actual hardware connection here
is likely to go through many transformations in the course of time, besides
which a detailed review of the subject is beyond the scope of this manual. (For
those interested, or if your work requires it, see a wizard.)
7-2. Command path
We will need a command path from the PDP-10 to the box to pass processing
element instructions, like start and stop instructions, connection plans, mode
changes, and the actual data to be put in the processing elements to set the
frequency of generators, etc. All of this information is passed during update
tick time. To do this there is a direct memory access channel (DMA) set up
between the box and the main core memory in the PDP-10. The central processing
unit (CPU) in the PDP-10 first finds a free block of core memory, then fills it
with instructions and update data for Sam, then passes the address to Sam. Then
Sam, during the processing of update ticks, reads it in through the DMA channel.
Page 13
LRNSAM The flow of data DRAFT
7-3. I/O path
Next, we will need a way to set the general status of the box, to turn it on and
off, and to pass it the address of the core memory discussed above. This is
called the I/O (for in/out) path. It is a relatively slower path than the DMA.
It is also used for diagnostic readback.
7-4. Read path and Write path
Here we want to take a sample of sound from the main computer and put it into
sum memory so it can be acted on by the processing elements. We can think of
the sample as just more update information. We have a problem, however: notice
that sum memory can only be addressed by processing elements. The solution is
to put some processing element into a special state whereby it just takes it's
update information and passes it directly to sum memory. Then and only then can
other processing elements reference that sample to modify it or whatever. The
generators were chosen for this task and the implementation is discussed later.
To return sound samples back to the computer we have the same problem. Only
processing elements can read sum memory. So again the generators have a special
mode that reads sum memory and passes the data to the DMA channel to the
computer.
7-5. DAC path
Lastly, how do we get samples from Sam to the DACs? Again we use generators to
read sum memory and pass the data to a bus to the DACs.
7-6. It's a Stream machine...
This is an appropriate time to mention that the box is a "stream" type device,
meaning that it can be running whether you are telling it what to do or not.
You don't have to fill up all the update ticks that you've asked for. You can
send less PROVIDED that you tell Sam not to expect any more update information
until some time which you specify as being so many ticks in the future. This
number can either be relative from the first tick or from the current tick.
Thus you can set up some state in the box and tell it to continue doing what it
is set up to do. The way to do this is to give it a DWELL command (See page
69). This command is passed to the box on an update tick and it instructs Sam
not to process any more update commands until it has done so many ticks. If you
don't give Sam an instruction when it expects one, it's called "command
Page 14
LRNSAM The flow of data DRAFT
underrun", and it can stop the box. Sam then sends an interrupt to the system
which halts your program. Actually, any process that gets out of sync with its
environment will cause or suffer underrun or overrun. There's less likelihood
that anything inside the box would get out of order (heaven help us!), than
between the system and the Samson box where things can get out of hand very
easily. If Sam is trying to get commands while the system is looking elsewhere,
or if there is a disk bottleneck (not unlikely) then you get command underrun.
There are other data errors that can also send interrupts such as arithmetic
overflow and problems relating to direct memory access. All interrupts are
handled through the I/O path.
Page 15
LRNSAM DRAFT
Section 8
_______ _
Sum memory
___ ______
Sum memory is the place that numbers are stored that are the output of
processing elements and are to be the input to other processing elements. It is
necessary that a value be read into a location in sum memory before it can be
read by any other processing element. Sum memory is made up of 256 locations,
numbered from 0 to 255. Addresses are passed to those processing elements that
are to read or write a number into that sum memory location.
It's called sum memory because any value added in during a pass is added to the
contents already there from the beginning of that pass.
8-1. This pass - last pass division
Sum memory is divided in two basic ways, producing four quadrants. One of the
divisions is between "this pass" sum memory and "last pass" sum memory. The
active processing elements on the current pass all write into this pass sum
memory and read from last pass. This means that all the outputs of any given
pass are in place before any reading of sum memory is done. After the
processing of all the outputs has taken place (which, you should remember, is
the definition of a pass), this pass sum memory is frozen and becomes last pass
sum memory. This is nice in that it means that you don't have to worry about
the order of computation of the processing elements inside of a pass. By the
time you get around to reading the output of any processing element all the
outputs of all the processing elements are available, ensconced in last pass sum
memory.
However, there is a special feature that modifiers can also read from this pass
sum memory (not to make things too confusing or anything), in which case the
ordering of the modifiers inside a pass does become critical. If, for instance,
____
the modifier that is trying to read a this pass sum memory location is executed
before the one that is supposed to be supplying the number, you get zeros.
Please notice an implication of the separation in time by one pass of reading
and writing into sum memory. Whereas the processing order within a pass is not
critical (with the exception above noted), the processing between passes is
__
critical if you are chaining a bunch of processing elements together.
Say maybe you want to FM modulate an FM modulated generator, meaning that you
have the output of one generator affecting the frequency of another generator
affecting the frequency of yet another generator. This setup is yet another
pipeline! So you must be sure to start the processing elements in order. Pass
Page 16
LRNSAM Sum memory DRAFT
1: the three generators crank and from generator 1 comes a valid number, but 2
and 3 produce nonsense; pass 2: generator 2 reads generator 1's output and gives
a valid result; pass 3: finally the valid results have trickeled through the
system. Had we not started up the generators in order, we would have lost a
sample and promulgated the delay down through the line. It may or may not
produce a fatal error, but it is surely a potential bug.
The last pass and this pass halves of sum memory alternate, one set being the
current this pass, then becoming last pass. Before it again becomes this pass
it is erased so that previous values don't linger. Therefore reading a sum
memory location without writing in it first will always return zero.
8-2. Generator - modifier division
The other basic division of sum memory has to do with which kind of processing
element writes where.
One half of "this pass" sum memory is dedicated to output from generators. That
means the generators have 64 locations in this pass sum memory that they can
write into. The other half of this pass sum memory (64 locations again) is
dedicated to the output of modifiers. For this pass sum memory this distinction
remains firm. However, either type of processing element can read any location
____
(128 of them) in last pass sum memory.
The modifiers have the ability, you recall, to read from this pass sum memory,
however, they are restricted to reading from the modifier quadrant. That makes
a total of 184 locations that are available for them to read from on a pass.
This can be so since a modifier only does one write into sum memory every two
ticks. So it has some extra time to do sum memory reads. Again, if a modifier
is reading from this pass memory, the ordering of the units must be done
carefully so that one is assured that the data is present in memory when you
want to read it. This requires keeping track of the extensive pipelining in the
machine, which we will not go into here.
8-3. Addressing sum memory
Here's what the processing elements have to communicate with sum memory: Each
generator that is running is given a location in sum memory into which it
deposits its generated sample. More precisely, there is a location in every
running generator which contains a word that is the address of a location in sum
memory into which that generator is to put its output. The location in the
generator that stores the sum memory address is called (appropriately enough)
SUM_MEMORY. Generator location SUM_MEMORY is therefore called a "port" to sum
Page 17
LRNSAM Sum memory DRAFT
memory. Generators likewise have one port to read from sum memory called FM.
This means that FM is a location in the generator which contains a word which is
an address of a location in sum memory from which the generator is to read data.
The modifiers have one port to write, also also called SUM_MEMORY (the assembler
can make the distinction of whether we are referring to the SUM_MEMORY location
in a modifier or generator as will be explained later), and two ports to read,
A_IN and B_IN.
The delay units have no direct port to the sum memory, but must be accessed by
the modifiers, which pass data to the delay units and read data from them. The
modifiers then transfer data to sum memory.
Page 18
LRNSAM DRAFT
Section 9
_______ _
Generators
__________
Generators are one of two main processing elements in the box. They do various
things, most important of which, they produce oscillations of various flavors
and frequencies and they control the amplitude of the oscillations through time.
The generators are divided into two parts: the oscillator is the side that makes
the waveform, and the side that controls the amplitude of the waveform is called
the envelope controller, or just envelope.
The generators generate waveforms and envelopes by two basic methods 1) logical
testing of a changing function (as in the case of pulse train and square wave)
or 2) table lookup (as in the case of sine waves and exponential envelopes).
The oscillator looks up values from a sine table to produce sinusoidal (and
cosinusoidal) waveforms. There are several things it must determine before it
knows where to look in the table, most obviously what the frequency is, the
phase, etc., etc.
The information the generator needs to produce its waveform is passed to it on
update ticks. The values sent are stored in locations in the generators, from
whence they are combined by processing ticks to produce the waveform.
Generators can receive new parameters for all of the locations listed in the
table below on the processing of one update tick. Depending on what mode the
generator is in, not all of this information may be needed or useful at any
given time. In addition, some locations have multiple functions and are used
for different things in different modes. (For instance, if you just want the
generator to produce sine (cosine) waves, you need not specify NCOSINES. If you
don't care about the initial phase of the wave, you need not specify ANGLE, etc.
More on this later.)
Although the oscillator part of the generator will be covered more fully later,
a little of its operating theory is necessary before discussing its variables.
The heart of the oscillator side of the generator is the ANGLE term. This is
the index which keeps track of where we are in the cycle of whatever waveform we
are producing. Angle is what is tested or used for table lookups that determine
the value of the sample. ANGLE should be thought of as an unsigned binary(1)
counting register that, as it is incremented from its lowest number (all zeros)
to its highest (all ones), represents the phase angle of one cycle of the
waveform being generated. The phase angle of zero is represented by all zeros
in the register, pi/2 is represented by 010000... (decimal.25, i.e. one quarter
of a cycle), pi by 10000...(decimal .5, the half-wave point), and when ANGLE is
_______________________
(1) for a discussion of number representations used in this manual see appendix
3.
Page 19
LRNSAM Generators DRAFT
all ones, the phase angle is about ready to start another cycle. ANGLE is
mostly formed of the number in FREQUENCY plus whatever was in ANGLE from last
pass. As FREQUENCY increments ANGLE, ANGLE rises until it reaches the point
where the next value from FREQUENCY would overflow the register, then modulo
arithmetic sets in as follows: the ANGLE register is cleared and the remainder
magicly appears as its new value, and we have started a new cycle of our wave.
Thus the size of the number in FREQUENCY determines the frequency (oftenness)
that ANGLE turns over. The EXPONENT term in the oscillator side does much the
same thing as ANGLE. All this is explained in greater detail later.
9-1. Generator parameters
In the tables below, there are generally two names for each location. There is
the internal name (denoted by the column marked Id) which is the name used in
the hardware description of the device and on the circuit diagrams for the
device, then there is the SAIL name (denoted by the column marked Definition)
which is how we refer to these things in SAIL. I will refer alternately to the
Id name and the Definition in the discussion of these things later, usually
puting the Id in parenthesis after the SAIL name, to acquaint you with both.
But you should understand that the Definition is what you will use in any
__________
program to refer to the particular location in the box, and the assembler that
actually runs the box will convert it into the equivalent Id code. The Size
column tells how many binary bits the location consists of. The Type column
says whether the number is: Signed, Unsigned, Different in different modes or
_ _ _
Inconsequential (can be considered either). The notations in the Function
_______________
column are elaborated more thoroughly later.
Page 20
LRNSAM Generators DRAFT
Id Size Type Definition Function
--------------------------------------------------------------------------------
GO 20 S SWEEP oscillator frequency sweep rate
GJ 28 I FREQUENCY oscillator frequency
GK 20 D ANGLE oscillator angle
GN 11 U NCOSINES number of cosines to be summed
GM 4 U SCALE binary scale of cosine or sum of cosines
GP 20 S RATE envelope rate of change
GQ 24 U EXPONENT envelope current value
GL 12 U ASYMPTOTE asymptote (DC offset of the envelope)
GSUM 6 U SUM_MEMORY sum memory location into which output is added
GFM 7 U FM sum memory address from which frequency
modulation data is taken
GMODE 10 MODE generator mode, including run mode and
type of oscillator and envelope processing
(see list of modes on page 22).
OSC_MODE sets oscillator field of SUM_MEMORY without
altering run mode or envelope mode.
ENVELOPE sets envelope field of SUM_MEMORY without
altering run mode or oscillator mode.
Fig. 3
____ _
Page 21
LRNSAM DRAFT
Section 10
_______ __
Generator run modes
_________ ___ _____
This is a list of general modes, affecting both the envelope and oscillator
halves of the generator. There are some additional modes that are specific to
the two halves which we'll get to in good time. The run modes are set up with
update ticks. Since the hardware names for the run modes are binary words, only
the SAIL definitions are given.
Definition Osc. run? Envelope run? Add to sum?
--------------------------------------------------------------------------------
G_INACTIVE no no no
G_PAUSE no no no
G_WAIT yes no no
x_RUNNING:
A_RUNNING yes yes yes
B_RUNNING yes yes yes
C_RUNNING yes yes yes
DATA_READ no yes yes
DATA_WRITE no no no
DAC_WRITE no no no
Fig. 4
____ _
10-1. INACTIVE
freezes both the oscillator part of the generator and the envelope side.
Actually, the generator continues to be processed, but the changing of
parameters is inhibited and it produces no output. Please note that this means
making a generator INACTIVE does not clear its memory. It retains the last
frequency and envelope it had before going inactive. In fact, generator memory
is never cleared except by explicit command to load new values. For this
reason, you have to set all the relevant parameters of a generator before you
turn it on, else you might be subject to a certain form of astonishment.
10-2. G_PAUSE
freezes both the oscillator and the envelope as does INACTIVE, but makes it
easier to restart it. There is a special command called "clear all pause bits"
Page 22
LRNSAM Generator run modes DRAFT
(see page 68) that will reinstate all PAUSing generators, whereas you can't
reinstate an INACTIVE generator except by an explicit change-mode command. This
is useful if, for instance, a bunch of generators were running, you want them to
stop, but you don't want to have to send them all individual mode change
commands to crank them up one by one which would waste update ticks. This way
also you are guaranteed that they will all restart later with the same phase and
envelope positions as where they left off. (But you must set up the pausing
generators one per update tick.)
10-3. G_WAIT
continues running the oscillator side but turns off the envelope side and stops
output. This is to let the oscillator keep in phase with any other generators
that may be running concurrently. It can therefore reenter later and still be
in sync. with the rest of the world. There is also a "clear all wait bits"
command (see page 68) later that encourages the use of G_WAIT mode.
10-4. x_RUNNING
This is the principal running mode. It has three slightly different ways of
doing its thing. Depending on which one you want, you substitute A, B or C for
the x. Basically, the only difference is in how the envelope side is set up,
the oscillator in all three modes just runs as usual.
But first a word about stickiness. Imagine the following horror story: the
envelope part of the generator is happily running along cranking out ever louder
samples. Comes a time when this is supposed to stop, but where's the update
command?: stuck somewhere in the operating system which is too hung up to get
the command out. The amplitude keeps growing until finally, we exceed the
largest amplitude the generator can produce and overflow... What a bummer...
(1)
But, the envelope side of the generator can be made "sticky," which means that
rather than overflow for lack of a command to tell it to stop, the envelope side
of the generator will "stick" at the last value it attained before it would have
overflowed. From that point on it will just continue to return that number until
the update arrives to tell it to do something else. Alternately the envelope
can be set to "free" mode, in which case the envelope wraps around and starts in
at the beginning again, and all ones become all zeros and we start rising again
(or all zeros become ones and we continue falling). A_RUNNING sets the envelope
in sticky mode. B_RUNNING sets it in free mode.
_______________________
(1) Arithmetic overflow occurs when the maximum positive number is exceeded or
the maximum negative number is exceeded. For instance, if you add anything to
the binary number 11111111111 . . . you get an overflow. Likewise if you
subtract anything from the binary number 000000000 . . . you get an overflow.
Page 23
LRNSAM Generator run modes DRAFT
There's another mode whereby arithmetic overflow in an envelope can be detected
and used as a "trigger" to cause the next numbered (the subsequent) generator to
go from G_WAIT to running. Meanwhile the triggering generator goes to G_WAIT
mode. This is what C_RUNNING does. The purpose of C_RUNNING is that by
dedicating, for instance, three sequentially numbered generators to one
"instrument", such that one generator computes the attack, the next the steady
state, and the last the decay, the "instrument" can, once it is started, run
without any further update commands. This ties up three generators, but
lightens the command stream by two update ticks per invocation of that
instrument. It may also be useful in cases like the one described above where
the general system load is such that the box couldn't get a command in time to
save a generator from command underrun. The user would still have a guarentee
that at least the note once started would continue along a prescribed course.
Thus we achieve graceful degradation.
10-5. DAC_WRITE
mode makes this oscillator be a port between the sum memory location addressed
by the word in FM and a DAC addressed by the word in SWEEP (GO). This is how
you get sounds out of the box. In this capacity the generator is similar to
OUTn locations in MUSCMP. (MUSCMP is the generic name for your favorite music
compiler.) What happens to SWEEP (GO)? This is an instance where the function
of the generator modifies what the variables mean. When the generator is in
DAC_WRITE mode, it is no longer a generator in the sense of producing samples.
It goes into the special mode of taking information from the sum memory location
whose address is in FM and passing it to the DAC whose location is in SWEEP
(GO). All other variables in this generator have no effect in this mode.
10-6. READ_DATA
mode makes the generator read data from main computer core memory through the
DMA channel, and deposit it in the sum memory addressed by SUM_MEMORY. This is
how we get sound samples (for instance) into the box from some other device.
The DMA channel is set up with a SET_OUTPUT command (discussed later). The CPU
figures out how to get the data to the generator. It is important to note that
the write data will be interleaved in generator number order.
_________
10-7. WRITE_DATA
makes the generator take samples from a sum memory location and write them into
main computer core memory through the DMA channel. This is how sound files are
Page 24
LRNSAM Generator run modes DRAFT
written onto the disk. You set up one generator in WRITE_DATA mode per channel
of output. Again, the write data is interleaved in generator number order.
_________
Page 25
LRNSAM DRAFT
DSK:GENDIA.XIP[DOC,MUS] GOES HERE
Page 26
LRNSAM DRAFT
Section 11
_______ __
Oscillator processing
__________ __________
In the diagram on page 26, the left side represents the oscillator processing,
the right half the envelope processing. SWEEP (GO), RATE (GP) and ASYMPTOTE
(GL) are constants which remain until altered by command, all other locations
are "refreshing", meaning that their value is recomputed every time the
generator goes through a processing tick. The old value is read by the process
just below it, and the old value is added to the new value coming down from
above. This is diagrammed as an arrow returning from the bottom to the top of
such locations such as ANGLE (GK).
If an oscillator is in a wave producing mode, the following is a simplified
description of what happens:(1) On the oscillator side, FREQUENCY (GJ) will have
the current frequency of the oscillator. SWEEP (GO) is a constant that is added
into FREQUENCY (GJ) every pass to augment or diminish the frequency to produce
sweeping or glissando. (If you do not want any glissando, you must explicitly
zero SWEEP.)
FM is the return port into the oscillator from the "last pass" sum memory. When
the oscillator is in any oscillatory mode (as opposed to the data channel
modes), FM reads the location in sum memory and the returned value is summed
with FREQUENCY thereby adding a modulating frequency (assuming that a frequency
was put, on the last pass, into the sum memory location that FM reads). The
value in FREQUENCY is not changed by the addition with FM. Note that each
generator, when in an oscillatory mode, always takes in a number through FM! If
______
you don't want anything here, then you have to give FM the address of some sum
memory location that is unused, so it will always return zero.
ANGLE (GK) stores the current angle. It is updated by adding FREQUENCY (GJ) as
computed above to the number in ANGLE (GK) left from last pass. ANGLE (GK) then
contains the current oscillator angle. It is used, depending on the mode of the
oscillator to determine the value of the output sample. It can be considered to
be the momentary phase angle of the waveform the generator is producing. But
the angle is not in terms of radians or degrees. It's simpler, actually: as the
number in ANGLE goes from all 0000...'s to all 11111...'s we complete one cycle
of our waveform. That is we go in radians from 0 to 2π. After 11111... ANGLE
wraps around to 00000... again. Thus when the number in angle is 01000..., we
are at the half-wave point. Likewise, RATE increments EXPONENT such that
EXPONENT goes from zero to full amplitude as its bits go from 0's to 1's.
When we finally have the value in ANGLE, what happens next depends on the mode
_______________________
(1) For the real version, see the Specification.
Page 27
LRNSAM Oscillator processing DRAFT
of the oscillator. If it is in a sine (cosine) wave producing mode, the ANGLE
(GK) is used to lookup a value on a sine (cosine) table. This is then the end
result of the oscillator side. This result is not complete, however, as the
output of the oscillator must still be scaled by the envelope side of the
generator. The envelope side is discussed in the next section, and the rest of
the modes are covered in more detail on page 36.
Page 28
LRNSAM DRAFT
Section 12
_______ __
Oscillator modes
__________ _____
These modes set the type of waveform the oscillator side of the generator will
produce.
Definition Function
--------------------------------------------------------------------------------
SAWTOOTH sawtooth wave
SQUARE square wave
SUM_OF_COSINES sum of cosines
PULSE_TRAIN pulse train
COSINE sine(ANGLE),normal sine wave
mode (with fm).
COS_FM cosine(FREQUENCY + FM)
special sine table lookup mode.
Fig. 6
____ _
12-1. COSINE
This mode is the standard sine wave oscillator with an fm input. Why cosine if
it produces sine waves? Well, that's what Pete Samson calls it... It really is
a SINE oscillator, but can be made into a cosine oscillator by setting the ANGLE
____
initially to π/2, which in binary would be 010000... (unsigned), the quarter-
wave point. (Kneadless to say, such phase tweaking can be perpretrated on any
waveform).
12-2. SAWTOOTH
For this, the oscillator merely has to pass on the oscillator angle directly
since the angle consists (for positive frequencies) of an increasing ramp
function. The wave starts at whatever you set ANGLE to be (maybe 00000...) it
goes from there to 11111..., then wraps around to 00000... and continues
increasing. (It would be decreasing if the FREQUENCY were negative.) Since ANGLE
here is considered to be the actual waveform passed by the oscillator, we should
think of the ANGLE term as being a two's complement signed number since we want
to represent the waveform as having a positive and negative domain as acoustic
waves do. Remember, the number from the oscillator side, regardless of how it
Page 29
LRNSAM Oscillator modes DRAFT
is arrived at, is multiplied by the output of the envelope side in two's
complement form. This illustrates how ANGLE can be considered signed or
unsigned depending on how it is used. When ANGLE is used to look up values on a
sine table the sine of ANGLE = 0000... will be zero no matter how we interpret
ANGLE. But it must be interpreted as a two's complement number if we intend it
to be the actual result of the oscillator.
12-3. SQUARE
The oscillator produces square waves by simply testing the value of ANGLE.
while ANGLE is ≥ 10000...(unsigned .5) the oscillator produces the value
01000... (signed). When ANGLE ≤ 01111...(unsigned), the oscillator produces
11000...(signed -.5). Again, the distinction between signed and unsigned is
that, here we use ANGLE merely as an internal counter, which in its raw state
might as well be unsigned, but the result of the test must be signed as it is
the output of the oscillator.
12-4. PULSE_TRAIN
During a pass in which ANGLE overflows a pulse of +.5 (01000..., signed) is
produced. Otherwise zero is returned. ANGLE wraps around (as always).
12-5. COS_FM
The oscillator output is determined by the sum of FREQUENCY (GJ) and FM. Notice
that we are taking the cosine of the frequency term, not the angle term, as we
would for a normal oscillatory mode. This is another case where terms are used
for something other than that for which they are named. What this mode does
basically is just look up whatever it finds in sum memory location FM in a
cosine table. The addition of the FREQUENCY term is peripheral. This mode,
therefore doesn't really oscillate as such. Its principal use is looking up
cosine values for certain filtering applications.
12-6. SUM_OF_COSINES
This allows the addition of many equal amplitude cosine waves together in one
generator, rather than having to dedicate several to the task. The result of
the sum of many equal amplitude cosines where the frequencies are integer
multiples is a band-limited pulse train. This means that the waveshape
Page 30
LRNSAM Oscillator modes DRAFT
approaches that of a pulse train, but the frequency bandwidth is no wider than
the difference of the frequency of the highest component minus the frequency of
the lowest. The frequency of the highest component of a regular pulse train is
infinite, therefore the bandwidth is also infinite.
12-7. A note on band limiting
A waveform that has an infinite number of partials is not band-limited (seems
reasonable, as tautologies go...). Since we are working in a sampled-data
world, any partials that exceed in frequency 1/2 the sampling rate are "folded-
over" back into the area below that limit and are included in the total spectrum
in strange and miraculous ways. The modes SQUARE, SAWTOOTH, and PULSE_TRAIN are
all non-band limited under all conditions. The SUM_OF_COSINES and COSINE mode
are band limited but SUM_OF_COSINES can still cause foldover if its highest
frequency component exceeds half the sampling rate. COSINE can also cause
foldover if its frequency exceeds half the sampling rate. PULSE_TRAIN always
has terrible foldover and is not usually what you want to listen to. SQUARE and
SAWTOOTH are technically not band limited, but the amplitude of the partials
falls off rapidly enough so that if the frequency is not too high, the higher
partials are not audible.
Page 31
LRNSAM DRAFT
Section 13
_______ __
Envelope processing
________ __________
On the envelope side, a 12 bit unsigned fractional binary number (a number
between 0 to 1) is produced which is used to scale the output of the oscillator
by multiplication. Since the result of the oscillator side is signed, the
multiplication is two quadrant.
13-1. EXPONENT and RATE
EXPONENT (GQ) stores the current value of the scaling number. RATE (GP) is a
constant which is added into EXPONENT (GQ) every pass to increment or decrement
it thus augmenting or diminishing the amplitude scaling number. Taken through
time therefore, EXPONENT (GQ) produces a ramp function from 0 to 1 (unless RATE
is exactly 0, of course, in which case EXPONENT remains constant). If the
generator is in one of the "sticky" modes, then the EXPONENT will not wrap
around. It will "stick" at the last value attained before overflow (which may
or may not be the largest number it can represent).
13-2. ASYMPTOTE
The value in ASYMPTOTE (GL) is added to (or subtracted from) the value in
EXPONENT and this final number is the one used to scale the output of the
oscillator. The effect of ASYMPTOTE therefore is to offset the the value in
EXPONENT by some constant value.
It can be cleverly used in conjunction with sticky mode as follows: Say you are
producing a very complex amplitude function, and you want to start the envelope
at .16 and let it increment by some value for a few passes to .95. Well, let's
remember the potential for disaster that can happen if the command that's
supposed to catch it at .95 doesn't arrive in time. It would appear offhand
that, since the value we want to stop at is well below overflow, we don't get to
use the sticky envelope feature. But! Let's put an initial value of .05 in
EXPONENT, and lets put .16 - .05 = .11 in ASYMPTOTE. The number in EXPONENT now
goes from .05 to overflow and will therefore stick if necessary, while the
actual output of the envelope side goes from .16 to .95 as usual.
If an exponential envelope curve is required, then EXPONENT (GQ) is used to look
up a value in a table of exponents which is then returned instead of returning
its linear value. The linear value used to do the lookup is kept, however to
compute the subsequent table address to lookup. The value of ASYMPTOTE is added
to (subtracted from) the value from the exponential table.
Page 32
LRNSAM Envelope processing DRAFT
The result of the envelope process is multiplied by the result of the oscillator
process. This constitutes a 2 quadrant multiply. The final result is an 18 bit
number and is added into sum memory right adjusted, sign extended. This final
result is added to whatever is the current value in this pass sum memory in
location SUM_MEMORY.
Note: when the output of a generator is being used as the modulating frequency
of another generator, the full 20 bit sum memory word is read into the carrier
generator's FM port and is added with the bits in FREQUENCY left adjusted. But
the default output of the modulating generator is 18 bits rignt adjusted. So we
must scale the amplitude of the modulating frequency to make it be left adjusted
in the 20 bit word in SUM MEMORY so its high order bit will line up with the
high order bit in FREQUENCY so as to add properly. This is done by multiplying
the amplitude of the modulating generator by 4, which has the effect in binary
arithmetic of left-shifting the entire number two places. This puts the high-
order bit of the modulating waveform into the 20th bit position in SUM MEMORY
and it is left justified.
Page 33
LRNSAM DRAFT
Section 14
_______ __
Envelope modes
________ _____
These modes specify the arithmetic operations of envelope processing. How we
use the "Definition" terms will be explaned when we talk about actually running
the box. Bascally, it consists of a predefined word full of bits which acts as
a flag to Sam to enable some mode.
Definition Function
--------------------------------------------------------------------------------
LPLUSQ Add EXPONENT (GQ) to the offset constant
in ASYMPTOTE (GL).
LMINUSQ Subtract EXPONENT (GQ) from offset constant
in ASYMPTOTE(GL)
-EXPONENT
LEXPLUS Add ASYMPTOTE (GL) to 2 and scale.
-EXPONENT
LEXMINUS Subtract ASYMPTOTE (GL) from 2 and scale.
Fig. 7
____ _
LPLUSQ: The value in EXPONENT is added to the constant in ASYMPTOTE.
LMINUSQ: The constant value in ASYMPTOTE (GL) is subtracted from EXPONENT (GQ).
LEXPLUS: The value in EXPONENT is used as a lookup in an exponential table and
the result is added to the constant in ASYMPTOTE. The exponent returned from
-EXPONENT*16
the table lookup can be defined as 2 . To see what's happening here,
first remember that EXPONENT takes values between 0 and 1. If EXPONENT is 0
0
then the value from the table will be 1 (2 = 1). If EXPONENT is 1/16, then the
-16
result is .5. If EXPONENT is 1, the result is 2 , taken as zero. Inbetween
values are exponential. We could also look at it in binary. The following is
all unsigned.
Page 34
LRNSAM Envelope modes DRAFT
EXPONENT: Result from table:
binary decimal binary decimal
0000 0000... 0 111 111 111 111 1
0001 0000... 1/16 011 111 111 111 1/2
0010 0000... 1/8 001 111 111 111 1/4
0011 0000... 001 111 111 111 1/8
0100 0000... 1/4 000 111 111 111 1/16
.
.
.
1111 1111... 1 000 000 000 000... 0
So in the binary representation, it is like the EXPONENT has a 4 bit integer
part (the high order bits) and an 8 bit fractional part(the rest of the word).
LEXMINUS: Exponential table lookup is done, and value is subtracted from the
constant.
Page 35
LRNSAM DRAFT
Section 15
_______ __
Shape-shifting generators
______________ __________
As you know, the generator parameters must do double duty sometimes depending on
whether they are acting as data paths or waveform generators. Here we get into
the guts of the issue.(1)
The generators function in a variety of ways in the box. Their principle
functions are 1) waveform production, 2) the transfer of data from sum memory to
CPU, 3) the transfer of data from CPU to sum memory, 4) the transfer of data
from sum memory to the DACs and 5) doing sine table lookups on data in sum
memory. All of this is done, without having to create new processing elements
and parameters, by making various existing generator parameters function in more
than one way. Each generator run mode uses different parameters in different
ways, hence the following "road map" which gives a close (though not definitive)
look at them.
Remember that the generators have two parts, an oscillator part and an envelope
part. The oscillator side of the generator is running if it is in any of
G_WAIT, A_RUNNING, B_RUNNING C_RUNNING and READ_DATA modes. It is not running
in INACTIVE, G_PAUSE, WRITE_DATA and WRITE_DAC modes. By "running" I technicaly
mean whether ANGLE is being updated or not. A special exception to this rule is
COS_FM where part of the oscillator is running (the sine table lookup part) but
the rest of it is not.
So here is a list of the generator parameters, and a description of what part
they play in the various run modes.
15-1. RATE
RATE (GO) is the oscillator frequency sweep rate. RATE stores a number which,
when the oscillator is running, is added to FREQUENCY (GJ) on every pass, This
allows FREQUENCY to rise or fall at a given rate. RATE functons as such in all
oscillator run modes. If the generator is in WRITE_DAC mode, then RATE is used
to store the address of the DAC, and its value is not added into FREQUENCY.
_______________________
(1) Note, this is not the order of the processing steps. For the definitive
processing order, as well as all the gory details, see the Programming
Specification.
Page 36
LRNSAM Shape-shifting generators DRAFT
15-2. FREQUENCY
FREQUENCY (GJ) stores the oscillator frequency. The data from sum memory
addressed by FM is added to the value in FREQUENCY in all oscillator modes. FM
will presumably address either frequency modulation data from some other
generator or will have data to be sent to a DAC. If the oscillator side is
running then RATE is also added to FREQUENCY (as described above, this
increments the frequency by a constant, for frequency slewing, or glissando).
15-3. ANGLE
ANGLE (GK). The primary function of ANGLE is to store the current position of
the oscillator in the cycle of the wave it is producing, called the oscillator
angle. This term is used as an index to lookup sine-cosine values on a table or
as the datum of a logic test for the straight line waveforms. However, it is
used this way only when the oscillator is running.
The principal exception to this is COS_FM. In COS_FM mode, ANGLE is not used at
all. Instead, the output of the oscillator is the sine of the sum of FREQUENCY +
<contents of FM>.
If the generator run mode is WRITE_DATA or WRITE_DAC then the data sent to the
CPU is the word addressed by FM.(1). The entire table is actually shifted one
half a position, as it were. This is because the CSC taken in SUM_OF_COSINES
mode is 1/SIN, and if we took the sin of 0 we'd get infinity. But by shifting
the sine table, this is mitigated. This shift won't screw you until you try to
do sine summation synthesis.]
15-4. NCOSINES and SCALE
N_COSINES (GN), SCALE (GM). These are relevant only for SUM_OF_COSINES mode.
N_COSINES specifies the number of cosines to be summed. SCALE gives a scale
factor for normalizing the summed result. (If you have a bunch of cosine waves,
remembering that they all have an amplitude of 1 at zero degrees, their sum can
begin to stack up, hence the need for normalization.) Actually, the
SUM_OF_COSINES algorithm should be normalized by 1/NCOSINES, but we don't have a
divider at that point in the processor, so we approximate it by just scaling the
result of the cosine summation formula by shifting the bits in the word right or
left, which you recall is the same as dividing or multiplying by a power of 2.
This gets us within a factor of 2 of the correct normalization, not perfect, but
acceptable.
_______________________
-13
(1) Note that in the sine table there is an implicit phase shift of 2π*2
Page 37
LRNSAM Shape-shifting generators DRAFT
Please note that SCALE should be set to zero when the oscillator mode is not
SUM_OF_COSINES, since a non-zero value in SCALE implies a subtraction of one.
(This is necessary for the SUM_OF_COSINES algorithm to get rid of the cos(theta)
term). Note that this also means that in SUM_OF_COSINES mode, SCALE should be
non-zero.(1)
15-5. FM
FM (GFM) has the address of a location in sum memory which the generator can
read. FM can read from either generator or modifier last pass. FM is read and
its value is added together with FREQUENCY in all oscillator modes. Please note
that if you don't want data from sum memory then you must give FM the address of
some location in sum memory which is never written into and will hence always be
zero.
15-6. SUM_MEMORY
SUM_MEMORY (GSUM) has the address of a location in sum memory into which the
generator writes its output. This output is generally added to the contents
already there from this pass. But if the run mode is READ_DATA, then sum memory
gets the final output of the generator PLUS the data read from the main computer
CPU. This REPLACES the contents of the sum memory location addressed (so its no
longer really sum memory).
15-7. RATE EXPONENT and ASYMPTOTE
RATE (GP), EXPONENT (GQ) and ASYMPTOTE (GL) are relevant only when the
oscillator is in a wave producing mode and the envelope is running as well. The
envelope side is running in A_RUNNING, B_RUNNING and C_RUNNING only. EXPONENT
is the envelope increment corresponding in function to FREQUENCY in the
oscillator. It can be augmented or diminished by a number in RATE just as
FREQUENCY can be changed by SWEEP. EXPONENT is either used directly as a linear
function or as an index to an exponential table used to produce exponential
envelopes. The resulting number returned from either path is added to
ASYMPTOTE, which is a constant used to offset the envelope ramp from the X axis.
_______________________
(1) For a discussion of sine summation synthesis see Godfrey Winham, Kenneth
Steiglitz, "Input Generators for Digital Sound Synthesis", J. Acoust. Soc. Am.,
__ _______ ____ ___
Volume 47, #2 (part 2) 1970, pp665-666.
Page 38
LRNSAM DRAFT
Section 16
_______ __
Magic numbers
_____ _______
Specifying frequency and duration values to the box needs a little explanation:
We know that MUSCMP requires scaling by a value called MAG. (MAG is the ratio
of the waveform table length to the clock rate, or in a typical instance:
512/12800 = .04). For instance, if a variable P3 is used to scale the frequency
of an oscillator and it has a frequency value in Hertz, the scaled number sent
to the MUSCMP oscillator would be MAG*P3.
In the box, FREQUENCY (GJ), the oscillator frequency parameters must be scaled
similarly to the way the frequency in MUSCMP is scaled by MAG. The magic number
28
which corresponds to MAG in the box is 2 /sampling_rate.
To see this, remember that FREQUENCY (which is 28 bits long) is really a number
that is added into the number in ANGLE every pass. As ANGLE goes from 0000...
to 1111... we go through a complete cycle of our waveform. Consider the case
where we have all ones in FREQUENCY. What happens to ANGLE every pass? It is
wrapped around exactly back to where it was last time. Thus the frequency
produced is logically equal to the sampling rate since ANGLE goes through a
complete revolution every pass. (Though in fact the samples that are seen don't
reflect this, since ANGLE always wraps around to the same place they never
change value.) Similarly, if FREQUENCY contains all zeros, the number in ANGLE
will not change, and the frequency of the generator is equal to zero (though by
inspection the values produced are indistinguishable from the above case where
the frequency is equal to the sampling rate). This means that if the number in
FREQUENCY is 100000... (unsigned), then we are producing a frequency of 1/2 of
the sampling rate, 010000... is 1/4 of the sampling rate, &etc. So you can see
that the frequency of a generator is a function of the speed at which FREQUENCY
makes ANGLE go through a complete cycle, and this in turn is controlled by how
many passes are accomplished (and therefore samples produced) per second.
For a concrete example, say we had a sampling rate of 12800 samples per second.
28 28 7 21
The magic number would then be 2 /12800 = 2 /(2 *100) = 2 /100 = 20971.52.
This number multiplied by some frequency will put the correct binary number in
6
FREQUENCY. For instance, 1/2 the sampling rate = 6400 Hz = 2 *100.
21
2
6 27
2 *100 * ------ = 2 , Equ. 4
100
Page 39
LRNSAM Magic numbers DRAFT
which in a 28 bit binary word is 10000... as in our example above.
If you were really awake right now you might be wondering how, if FREQUENCY is
all ones, that it turns ANGLE over exactly once since FREQUENCY has 28 bits
whereas ANGLE has only 20? It should, by rights, turn it over 8 bits farther,
no? It's like this: the low order eight bits (the rightmost bits) of FREQUENCY
are not added into ANGLE at all, rather, the high order 20 bits of FREQUENCY are
all that are added to ANGLE. So what good are the low order 8 bits? The point
is that we want to try to get as fine a change of frequency as possible when
FREQUENCY is being incremented by SWEEP so that we can represent extremely slow
changes in frequency. To this end, SWEEP is added into FREQUENCY right
adjusted, meaning its 20 bits are added to the righthand, or low order, 20 bits
of FREQUENCY. But since only the left 20 bits of FREQUENCY are used to add to
ANGLE, the right eight bits act as an accumulator of subtler changes of
frequency than can be represented by 20 bits. When the eight bits finally sum
up enough to toggle the 9th bit, then the amount is registered as part of the
new 20 bit frequency increment sent to ANGLE. Thus we get eight more bits of
frequency precision with respect to glissando, without having to go to the
expense of making ANGLE lookup in a 28 bit sine table.
One further word of interest: FREQUENCY is a 28 bit word, but the largest word
that can be loaded in one update tick is 20 bits. So Samson has given you the
option of loading just 20 bits or all 28 by taking two update ticks to do it.
All this is done automatically by the software. If the number you want sent is
less than or equal to 20 bits it loads only that many. But if this is the case,
then FREQUENCY acts like a 20 bit word (the sign bit is extended). The magic
20
number then becomes 2 /clock_rate.
All processing that results in output is in terms of passes. This is the same
as MUSCMP, where each output sample is derived from one "pass" through the code
which comprises the instrument. One difference is the treatment in scaling of
the duration of envelopes. In MUSCMP, envelopes are usually generated by
oscillators, which means that if a variable P2 has a value of absolute time in
it, the scaled number sent to the oscillator would be MAG/P2. However in the
box, the scaling is simpler. Since the envelope side of the generators returns
a new envelope value on every pass, we only need to let the envelope run as many
passes as it would take to get the value we want: so the number of passes that
the envelope would be allowed to process would be P2*clock_rate. We would
increment the size of the envelope by P2/clock_rate on every pass to obtain a
final value of 1 at the end of P2*clock_rate passes.
Another useful formula is for SWEEP, the frequency sweep rate. To get a
frequency sweep from Frequency_1 to Frequency_2 in one second, put Frequency_1
times the box's magic number in FREQUENCY (GJ), and the difference of
28 2
Frequency_2 - Frequency_1 times another magic number, 2 /clock_rate , in SWEEP.
That would be:
Page 40
LRNSAM Magic numbers DRAFT
28
FREQUENCY ← Frequency_1*2 /clock_rate, Equ. 5
and
28 2
SWEEP ← (Frequency_2 - Frequency_1)*2 /clock_rate . Equ. 6
The derivation of the formula for SWEEP is as follows:
28 28
(Frequency_2*2 /clock_rate) - (Frequency_1*2 /clock_rate)
SWEEP ← ------------------------------------------------------,
clock_rate
28
(Frequency_2 - Frequency_1)*2 /clock_rate
SWEEP ← ---------------------------------------------,
clock_rate
28
(Frequency_2 - Frequency_1)*2 *1/clock_rate
SWEEP ← -----------------------------------------------,
clock_rate
28
SWEEP ← (Frequency_2 - Frequency_1)*2 *1/clock_rate*1/clock_rate,
28 2
SWEEP ← (Frequency_2 - Frequency_1)*2 /clock_rate . Equ. 7
To make this sweep happen in some other than one second, simply divide the above
number by the desired duration.
Page 41
LRNSAM DRAFT
Section 17
_______ __
Modifiers
_________
17-1. Modifier parameters
Modifiers do all kinds of things. Again, what you have them do determines which
of the following terms are relevant. They have the following locations:
Id Size Definition Function
--------------------------------------------------------------------------------
M0 30 COEFF0 coefficient
M1 30 COEFF1 other coefficient
L0 20 TERM_0 running term
L1 20 TERM_1 other running term
MIN 8 A_IN read address in sum memory
MRM 8 B_IN other read address
MSUM 7 SUM_MEMORY write address in
sum memory
ADD_SUM_MEMORY same as SUM_MEMORY.
REPLACE_SUM_MEMORY also has write addr.
but result replaces sum mem. value
MMODE 9 MODE run mode of modifier
and scale factor for the multiplies
A_SCALE scales COEFF1. (That's right A↔1!?!)
B_SCALE scales COEFF0.
FUNCTION same as MODE.
Fig. 8
____ _
17-2. Running terms
TERM_0 and TERM_1 are "temporary" locations dedicated to that particular
modifier which will have as their contents whatever was last stuffed into them.
The machine never resets them to zero as it does to sum memory. The main use of
the running terms is in certain of the modifier procedures which require
information to be carried over from the calculations performed on the last pass.
Filtering is an example of this, as is random number generation. These running
terms are also used when there is not enough time during one tick to do all the
computations, so a temporary location is needed to hold the partial results over
Page 42
LRNSAM Modifiers DRAFT
to the next pass. The running terms live inside their particular modifier.
They are not cleared on every pass as is sum memory. They always have the last
value that was written into them, and they divulge it only to their associated
modifiers. You can, of course initialize them by using an update tick to load
them, which you would want to do, for instance, for random number generaton.
17-3. Coefficient terms and Read terms
COEFF0 AND COEFF1 retain the number deposited in them across passes thereby
acting as constants. Their usual function is to scale the running terms or the
read terms, A_IN and B_IN. The read terms, as in the generators, contain the
address of a location in sum memory, and all arithmetic operations with them act
on the value of the number in sum memory that they address.
17-4. Scaling terms
The product of a coefficient term by either a running term or a read term can be
scaled by A_SCALE and B_SCALE to make it bigger. Making the product smaller
_______
than one is simple since normally the multiplies will always be in fractional
________
range (-1.0000... to +.9999...). (The exception is integer mixing, discussed on
page 46) Scaling is the only way to make the result of the multiply bigger than
one. Scaling is done by left-shifting (LSH) the multiplicand by one, two three
or four places. The effect this has is to raise the multiplicand by the
following respective powers of 2: 1, 2, 4, 8. To see this, take binary 0010
which is decimal 2, and LSH it by 2 producing 1000 = 8. Notice that the scaling
terms are put under MODE. It worked out that there was enough room left in the
MODE command to accommodate this extra baggage. So you set the SCALE terms with
the power of 2 you want, and that information is tacked on to the rest of the
MODE command and shipped off to the box. Please note that A_SCALE scales any
multiplication done with COEFF1, while B_SCALE scales any multiplication done
_
with COEFF0.
_
17-5. Mode
This defines the algorithm that the modifier will use. An alternate word for
this is FUNCTION since for modifiers that we are selecting is a particular
function or algorithm or process, whereas the word MODE might better be used for
setting the mode of an oscillator. But the two terms are equivalent, and the
SAIL procedures that handle them know whether the MODE selected is for a
modifier or generator. For a complete account of the various flavors, see
section 18.
Page 43
LRNSAM Modifiers DRAFT
17-6. Sum memory
Called the same as in the generators, but the assembler knows which one is
specified by context. There are three terms: SUM_MEMORY is the same as
ADD_SUM_MEMORY and for both, the result of the modifier procedure is added to
the preexisting contents (if any) in the sum memory location addressed. But
modifiers also can have their output replace the contents of sum memory, hence:
REPLACE_SUM_MEMORY.
17-7. Initializing
It is important to note that the first time you call a modifier procedure that
uses any of these terms, especially the running terms, they will have some
leftover numbers in them that will not be meaningful in terms of your new data,
but that will no doubt affect how that data is subsequently handled by the
modifier. Therefore it is first necessary to initialize all of these locations
to some meaningful value. This goes as well for the generators and delay
memory.
Page 44
LRNSAM DRAFT
Section 18
_______ __
Modifier procedures
________ __________
The name of the process is followed by its reserved definition followed in turn
by an explanation. Unless otherwise stated, the computation is in 20 bit two's
compliment, fixed binary point notation. Fixed binary point is a "fractional
notation" in that it represents only fractional numbers. Decimal representation
of the binary range is from -1.000... to +.999...
18-1. Inactive
M_INACTIVE
Dead to the world. As with generators, being inactive does not clear its
memory. The parameters (COEFF0 and COEFF1 and such) will be set to whatever
they were before the modifier went inactive. It will also still occupy its
place in the ordered processing of modifiers and use up two ticks.
18-2. Mixing
MIXING
Result ← COEFF0*A_IN + COEFF1*B_IN.
Signals in A_IN and B_IN are multiplied by COEFF0 and COEFF1 respectively and
added together. The COEFF terms act here and in many other of the modifier
procedures as gain factors for the weighting of the respective input terms. The
result is placed back in sum memory location referenced by MSUM. A_IN and B_IN
are locations which contain addresses of numbers in sum memory whose values are
looked up for these calculations. A_IN and B_IN will usually be used to lookup
sum memory locatons which will usually contain current instantanious values of
waveforms being created by generators (or whatever). A_IN and B_IN can also be
values that are the result of modifier processing which are then picked up by
other modifiers, etc. The multiplications are in fractional form as discribed
above.
Page 45
LRNSAM Modifier procedures DRAFT
18-3. Integer mixing
INT_MIXING
Result(integer) ← COEFF0*A_IN + COEFF1*B_IN.
Computation is the same as for fractional mixing above except that integer
multiplying is done. The difference is as follows: In fractional notation,
since all numbers are considered to be between .999... and -1.000.... the
result of a fractional multiplication will never be greater than this range.
That is, if you multiply the largest fixed binary point number you can represent
by another one equally large, the result is still that number. There is no
overflow. For instance, if you have a number 5 bits long, and you multiply
fixed binary point numbers .11111 by .11111 you get .11111. However, if you
multiply binary integer 00010. by 00010. you get 00100. You can see that if
you kept this up, you would get a result that is larger and larger: 00100*00100
= 10000, etc. (Eventually you would get overflow, which means that the
significant bits are to the left of the left-most place for them and the value
is therefore greater than the space available for it.) So, now that you've read
all this, the point is only that integer mixing allows you to make numbers
bigger while multiplying, while in fractional mixing above, the numbers only get
smaller. The other way to do this is of course to use the SCALE terms discussed
above.
18-4. Latch
LATCH
Result ← TERM_1. If COEFF1*B_IN ≠ 0 then TERM_1←A_IN.
The modifier gives as its result whatever was in TERM_1 from last pass. Then it
looks to see if it should sample a new value. If COEFF1*B_IN is not 0, the
condition is then true and it looks up the value of A_IN and replaces TERM_1
with it. Otherwise, TERM_1 hangs around until next time. The point of this
function is to wait for a triggering condition to be true, then sample a
changing variable being stored in sum memory (like to look at a sine wave being
fed into sum memory) and then to return the value sampled. If the condition
becomes not true then the value of the last sample taken before the condition
became false is returned. This value will be returned repeatedly until the
condition is true again at which time a new sample will be taken and returned.
(Since a running term is used, it is necessary to clear it first.)
Page 46
LRNSAM Modifier procedures DRAFT
18-5. Signum
SIGNUM
If COEFF0*A_IN < COEFF1*B_IN then the result ← -1 (integer)
else if the multiplicands are equal then the result ← 0
else if COEFF0*A_IN > COEFF1*B_IN then the result ← 1 (integer).
This tests the sign of the expression. It is similar to the Fortran "numerical
if" statement in that it allows one of three subsequent forks to be taken as the
result of an expression being >, =, or < 0. The result is in integer form.
18-6. Zero-crossing pulser
ZERO_CROSSING_PULSER
If (B_IN*COEFF0)*(TERM_1*COEFF1) ≤ 0 then result ← - 1
(all bits on) else result ← 0. TERM_1 ← B_IN*COEFF0.
This one figures out waveform zero crossings. It looks up two consecutive
points in the waveform time domain and asks whether their product is negative.
That is, if you have two samples in a waveform where the first is negative and
the second is positive, or vice versa, your waveform must have crossed zero.
Hence the two samples surrounding a waveform zero crossing will be represented
by numbers of alternate signs. The product of such numbers is negative while
the product of numbers of homogenous sign is positive. If the product is
negative (a zero crossing was detected) a -1 (all bits on) is returned, else 0
(all bits off). (Another possibility is if one of the two values of the
waveform is 0 e.g. if values were 1 and 0. The product would be 0, so there is
a trap for this also.)
18-7. Minimum
MINIMUM The lesser of A_IN*COEFF0 or B_IN*COEFF1 is returned.
18-8. Maximum
MAXIMUM The greater of A_IN*COEFF0 or B_IN*COEFF1 is returned.
Page 47
LRNSAM Modifier procedures DRAFT
18-9. Amplitude modulation
AMPLITUDE_MODULATION
Result ← TERM_1*COEFF1. TERM_1 ← A_IN*((B_IN+1)/2).
This basically multiplies two waveforms together such that B_IN is used to scale
the amplitude of A_IN. This is done by multiplying the number to be scaled by
another which has values between 0 and 1. If this scaling is done through time,
the overall result is of the amplitude of A_IN being dynamically modulated by
B_IN. To do this, we first need to scrunch B_IN so its value lies between 0 and
1. But B_IN is a binary point number which has values between .999... and
-1.000... So to convert B_IN, first add 1 to B_IN, to make its range go from 0
to 2. Then divide that by 2 to make it go between 0 and 1. Now we multiply
A_IN*B_IN and return the product. This process is repeated for every point on
the waveforms A_IN and B_IN. The steps the box takes to do this are as follows.
The value TERM_1*COEFF1 is returned where TERM_1 was determined the last time
around to be A_IN*((B_IN+1)/2). This process constitutes a two quadrant
multiply in that the sign of A_IN is significant but that of B_IN is not (B_IN,
being scaled between 0 and 1 will always be positive). So, if we plotted the
point (A_IN,B_IN) on a graph, any product of A_IN and B_IN would only need two
quadrants to be represented.
18-10. Four-quadrant-multiply
FOUR_QUAD_MULTIPLY
Result ← TERM_1*COEFF1. TERM_1 ← A_IN*B_IN.
TERM_1 scaled by COEFF1 is returned. A new TERM_1 is then computed as A_IN*B_IN
and stored in TERM_1 to be multiplied by COEFF1 next time around. Here, both
the signs of A_IN and B_IN are significant, and the product could be in any of
the four Cartesian quadrants. For synthesizer people, this is the ultimate ring
modulator.
18-11. Uniform noise
U_NOISE
TERM_1 ← Result ← COEFF0*TERM_1 + TERM_0.
We know that if we multiply integers, sooner or later, if we don't stop, we will
overflow, and the significant digits or bits of the product will be lost off the
Page 48
LRNSAM Modifier procedures DRAFT
left edge of our remaining number. That leaves only insignificant ones! So
these low order, insignificant bits are then returned as a pseudo-random number.
These numbers taken over a period of time produce white, or uniform noise. The
procedure adds TERM_0 to the integer product of TERM_1*COEFF0 and returns the
result. Overflow is purposefully ignored. It overwrites TERM_1 with this
result, to be used next time around. The one tricky aspect of this method is
that the numbers used to prime the procedure must be chosen with care in this
system. Andy Moorer has the following remarks about the process:
To make a modifier make pseudo-random numbers, you have to choose
COEFF0 (the multiplier), TERM_0 (additive constant) and TERM_1
(starting random number). Knuth(1) has a whole chapter on this sort
of thing. Probably the best you can do for these things is the
following:
1) COEFF0 is 30 bits long, but only the upper 20 bits are used in
the multiply. This being the case, it can be a 20 bit number chosen
essentially at random, but it ought to have some middle bits on and
it ought to end in 101 (for maximum potency). An example is this:
00000100110100100101 (octal 46445). (Note that since the
coefficient is really 30 bits long, when we pass the number as full
word data we have to stick ten more zeros on, which gives us octal
115112000.)
2) This being the case, TERM_0 should be set to zero (unless you
know what you are doing). Setting it non-zero doesn't make the
number any more "random".
3) The starting random number should be set to something non-zero,
like the date or time, your telephone number, pi, or whatever. It
should be a whole word full of bits.
18-12. Triggered uniform noise
TR_U_NOISE
Result ← COEFF0*TERM_1 + TERM_0. If B_IN*COEFF1 ≠ 0 then
TERM_1←result.
A new noise value is calculated as in U_NOISE above only when the "trigger",
B_IN*COEFF1, is not 0. This allows the production of noise values to be
dependent on some state in the box rather than requiring a command to get it
going or stoped, thereby lightening the command stream. Priming of the terms of
this procedure should be done according to the rules for U_NOISE above.
_______________________
(1) Knuth, The Art of Computer Programming, Vol. II.
___ ___ __ ________ ___________
Page 49
LRNSAM Modifier procedures DRAFT
18-13. Threshold
THRESHOLD
If COEFF0*A_IN + TERM_0 < 0 then result ← 0 else if ≥ 0 then result ←
COEFF1*B_IN.
This allows one to detect when a signal crosses some threshold. The tested
value is A_IN*COEFF0 + TERM_0. If it is less than 0 then 0 is returned, else if
it is greater than or equal to 0 then B_IN*COEFF1 is returned. B_IN*COEFF1 has
nothing to do with the value of the tested expression but could be made to
evaluate to a constant acting as a trigger to some other process or since we
have B_IN in the term it could be some other waveform which would then become
the output of the modifier.
18-14. Invoke delay unit
INVOKE_DELAY_UNIT
(B_IN(low order 5 bits) ← Unit#.)
result ← TERM_0 + TERM_1*COEFF0;
TERM_0 ← <delay_memory_value from delay unit>;
TEMP0 ← A_IN + <delay_memory_value>*COEFF1;
(<wait for next pass>)
TERM_1 ← TEMP0;
(<wait for next pass>)
<value sent to delay unit> ← TEMP0;
Delay units are called from modifiers. The delay units are discussed on page
61. The modifier acts like a processor for the delay units, and the delay units
only do the clerical work of stuffing and retrieving data. The processing that
the modifier does to the data sent to the delay units is as folows:
The address of the delay unit to be accessed this pass is fetched from the sum
memory location addressed by B_IN. At this point, TERM_0 has the datum that was
read from delay memory three passes ago. This is added to the product of TERM_1
and COEFF0. TERM_1 has the datum that was read from sum memory two passes ago.
(If you don't want this, just set COEFF0 to zero, of course.) The result of this
sum is then returned to the sum memory location addressed by SUM_MEMORY.
Next we receive a new delay memory datum from the delay unit and deposit it in
TERM_0.
Then we process the data to be written to delay memory. This takes several
passes, therefore we must use TEMP0 which is an internal register accessed by
Page 50
LRNSAM Modifier procedures DRAFT
the modifier that is not cleared from pass to pass. TEMP0 is given the sum of:
1) a word from sum memory addressed by A_IN (presumably the next sound sample to
be reverberated, etc.) and 2) the product of the current datum from sum memory
and COEFF1. This takes a pass. On the next pass TERM_1 is given the value in
TEMP0. On the next pass TEMP0 is passed to the delay unit to be deposited in its
memory.
Page 51
LRNSAM DRAFT
Section 19
_______ __
Filtering algorithms
_________ __________
Various flavors of filtering are available. Here is the barest outline,
starting with a SAILish version of the code performed. Section 19 has a little
more on how to use them, written by Andy Moorer.
19-1. One pole
Called as ONE_POLE mode to a modifier. Sam does the following loop:
LOOP: Result[k]←COEFF1*TERM_1 + COEFF0*B_IN;
TERM_1←Result[k];
<increment k and go to LOOP>.
Or, the formula could be given as:
Result[k]←COEFF1*Result[k-1] + COEFF0*B_IN[k],
where k is the number of the current sample, B_IN[k] is the location of the
current input sample, COEFF0 and COEFF1 are gain factors. The result is formed
of TERM_1*COEFF1 + B_IN*COEFF0, then TERM_1 is replaced by the result, to be
processed on the next pass. In this way TERM_1 becomes the k-1'th sample.
19-2. One zero
ONE_ZERO
Result←TERM_1*COEFF1 + A_IN*COEFF0, then TERM_1←A_IN. Or:
Result[k]←A_IN[k]*COEFF0 + COEFF1*A_IN[k-1].
Page 52
LRNSAM Filtering algorithms DRAFT
19-3. Two poles
TWO_POLES
LOOP: Result[k]←COEFF1*TERM_0 + COEFF0*TERM_1 + A_IN;
TERM_0←TERM_1;
TERM_1←Result[k];
<increment k and go to LOOP>.
This is like saying
Result[k]←COEFF0*Result[k-1] + COEFF1*Result[k-2] + A_IN.
19-4. Two zeros
TWO_ZEROS
LOOP: Result[k]←TERM_0*COEFF1 + TERM_1*COEFF0 + A_IN; TERM_0←TERM_1;
TERM_1←A_IN; <increment k and go to LOOP>. This is like saying
Result[k]←A_IN[k] + COEFF0*A_IN[k-1] + COEFF1*A_IN[k-2].
19-5. Two poles COEFF0 variable
TWO_0POLES
LOOP: Result[k]←TERM_0*COEFF1 + TERM_1*COEFF0 + A_IN; TERM_0←TERM_1;
TERM_1←Result[k]; COEFF0←COEFF0 + B_IN; <increment k and go to LOOP>.
19-6. Two poles COEFF1 variable
TWO_1POLES
LOOP: Result[k]←TERM_0*COEFF1 + TERM_1*COEFF0 + A_IN; TERM_0←TERM_1;
TERM_1←Result[k]; COEFF1←COEFF1 + B_IN; <increment k and go to LOOP>.
Page 53
LRNSAM Filtering algorithms DRAFT
19-7. Two zeros COEFF0 variable
TWO_0ZEROS
LOOP: Result[k]←TERM_0*COEFF1 + TERM_1*COEFF0 + A_IN; TERM_0←TERM_1;
TERM_1←A_IN; COEFF0←COEFF0+B_IN; <increment k and go to LOOP>.
19-8. Two zeros COEFF1 variable
TWO_1ZEROS
LOOP: Result[k]←TERM_0*COEFF1 + TERM_1*COEFF0 + A_IN; TERM_0←TERM_1;
TERM_1←A_IN; COEFF1←COEFF1+B_IN; <increment k and go to LOOP>.
19-9. A little digital filtering theory
(...never hurt anybody). Now I bet you want to know how to use these things,
right? Well, filters are entirely specified by their frequency responses, so
let's take a look: In what follows, w is the radian frequency (2πf) of a pure
sinusoid applied to the filter to determine its frequency response. T is the
sampling interval, or 1/clock_rate.
2
ONE POLE: H(w) = COEFF0/sqrt(1+COEFF1 -2*COEFF1*cos(wT)) Equ. 8
2 2
ONE ZERO: H(w) = sqrt(COEFF0 +COEFF1 +2*COEFF0*COEFF1*cos(wT)) Equ. 9
2
or H(w) = COEFF0*sqrt(1+R +2*R*cos(wT)) where R=COEFF1/COEFF0 Equ. 10
2
TWO POLES:H(w) = 1/sqrt((1-COEFF0*cos(wT)-COEFF1*cos(2wT))
2
+(COEFF0*sin(wT)+COEFF1*sin(2wT)) ) Equ. 11
2
TWO ZEROS:H(w) = sqrt((1+COEFF0*cos(wT)+COEFF1*cos(2wT))
2
+(COEFF0*sin(wT)+COEFF1*sin(2wT)) ) Equ. 12
These expressions won't mean much without some explaination. Let's look at the
ONE POLE case. Since cos(0)=1, H(0)=COEFF0/|(1-COEFF1)|. At half the sampling
rate, wT=π so cos(wT)=-1, thus H(2πclock_rate/2)=COEFF0/|(1+COEFF1)|. So we see
that the frequency response starts at H(0) and varies roughly cosinusoidally to
H(2πclock_rate/2). If COEFF1<1, then this means that for positive COEFF1, this
is a low-pass filter (accentuates frequencies around zero) and for negative
Page 54
LRNSAM Filtering algorithms DRAFT
COEFF1, it is a high-pass filter (depresses frequencies around zero). Note that
if COEFF1=1 or -1, you get a filter the frequency response of which goes to
infinity either at 0 or 2πclock_rate/2. This is not recommended.
For ONE ZERO, it is the same deal except that it is the ratio of COEFF1 to
COEFF0 that sets the frequency response and the sign is inverted. Positive
COEFF1 makes a high-pass and negative COEFF1 makes a low-pass.
TWO POLES: Now the really interesting (and hard to explain) stuff comes when we
get to two of everything. (double your pleasure, double your fun!...) The only
way to deal with a two pole or two zero filter is to put it in canonical form
(that word again!). Here is the canonical form for the TWO POLES case:
2 2
H(w) = G/sqrt((1-2*R*C*cos(wT)+R *cos(2wT)) +(-2*R*C*sin(wT)
2 2
+ R *sin(2wT)) ) Equ. 13
2
where G=1, COEFF0=2*R*C, and COEFF1=-R .
Without further explaination, this is a resonator (formant filter). Its resonant
frequency is determined by C, which is cos(2πfT). Its bandwidth (sharpness) is
determined by R which must be less than one for stability. Typical values would
be R=.95 for a fairly sharp filter and R=.999 for a filter that is so sharp that
it will "sing" at its resonant frequency almost by itself. To use such a filter,
you choose the resonant frequency, f, choose the bandwidth, R, the compute
2
COEFF0=2*R*cos(2πfT) and COEFF1=-R . This will give you that filter.
We have, in this description, sidestepped the issue of scaling. The gain of this
filter at resonance is
2 3 4
H(2πf) = G/sqrt[4(1-cos(2πfT))((1-R) -(1-R) )+(1-R) ]. Equ. 14
This looks a lot better if you substitute something, like D, for the term (1-R):
2 3 4
H(2πf) = G/sqrt[4(1-cos(2πfT))(D -D )+D ]. Equ. 15
For most purposes, you can normalize a filter by multiplying it by (1-R). For
higher frequencies, you need a smaller factor. The only problem is that there
aren't any more multiplies left in a modifier (no COEFF2, just COEFF0 and
COEFF1). This means that you have to scale the signal going into the filter by
(1-R) first. Don't forget this, because otherwise you will get lots of overflow.
Page 55
LRNSAM Filtering algorithms DRAFT
Note also that COEFF0 and COEFF1 are really less than 1.0. This being the case,
he said inquisitively, how can we possibly synthesize 2*R*C which can be as
large as 2? You do that using the scaling specified in the mode word MMODE. The
right four bits of the mode are the scaling for the multiplies. You can scale
the result of the multiplication by COEFF0 and the multiplication by COEFF1 by a
power of two. If we call the right 4 bits of the mode AABB, then AA or BB set
to 0 specifies no scaling, 1 means scale by factor of 2, 2 by factor of 4, and 3
by 8. AA is the scaling for the multiply with COEFF1, BB is the scaling for the
multiply with COEFF0. This means that we can get 2RC by setting COEFF0 to RC and
setting the scaling for the multiply by COEFF0 to 1 (factor of 2).
Now normalizing the response at resonance to 1 may or may not be exactly the
right thing to do. The problem is that this only makes sense if your signal is a
narrow band signal, like a pure sinusoid or something. Otherwise, normalizing
by the gain at resonance is too much attenuation. Unnormalized gives too much
gain and normalizing by the gain at resonance is too much attenuation, so
anywhere in between (like multiplying by (1-R)) is probably reasonable. Figures
9 and 10 show filter responses for two-pole resonators at three different center
frequencies and a number of different bandwidths. Figure 9 shows a normalized
filter. Notice that the gain is now 0 dB (1.0) at the center frequency, but for
high values of R, the peak is really narrow. Figure 10 shows the unnormalized
filter responses. Note that at high values of R, the filter can emphasize the
signal by as much as 40 dB.
Normalizing things with zeros rather than poles is much easier. The maximum
response for a filter with zeros occurs either at zero frequency or at half the
sampling rate, and this maximum is guaranteed to be less than 4.0 (for a real
notch filter, rather than two first-order filters multiplied together). Thusly,
you can pretty much ignore the scaling of an all-zero filter because you
probably won't get too screwed by it. Figure 11 shows the responses of two-zero
filters (or antiresonators, as we might call them).
And not to leave out the first order filters, figures 12 and 13 show the
responses of one pole and one zero filters. Here the maximum and minimum values
occur only at zero and at half the sampling rate. In all these plots, the
horizontal axis represents frequency from zero to half the sampling rate. You
can see that positive values of R for the one pole case represent low-pass
filters. The frequency responses of all-pole filters are just the inverses of
those for all-zero filters. Isn't that handy?
Page 56
LRNSAM Filtering algorithms DRAFT
FPIX.XGP[DOC,MUS] goes here.
Page 57
LRNSAM DRAFT
Section 20
_______ __
Delay Units
_____ _____
Delay units are called from modifiers by invoking the modifier in DELAY mode (in
a similar sense as invoking a modifier in, say, AMPLITUDE_MODULATION mode). For
an example of calling a delay line see page 93.
The delay units thus invoked have two possible modes, table lookup, and delay
line. There is (yet another) storage area that the delay units address called
delay memory. It contains (in our case) 48k of 20 bit words. (A full house
would contain up to 65,536 20 bit words.)
In delay line mode, the modifier sends the delay unit a word of data which is
then stored in the current location in the delay array. The old value in that
location is retrieved before the new word is written in. This old word is then
sent back to the modifier as the delayed signal. When the array is full of
words read from the modifier, the array pointer is reset to the base address and
it goes down the line again, retreiving the data in the location before writing
new data received from the modifier into it.
Id Definition Function
------------------------------------------------------------------------------
P MODE Delay unit mode
Z DELAY_LENGTH Delay line unit length
SCALE or table address scale factor
Y INDEX Running index of the delay array
X BASE_ADDRESS Base address of delay array
Modes:
D_INACTIVE dead
DELAYLINE does the processing described below
TABLE_LOOKUP see below
ROUND_TABLE_LOOKUP see below
Fig. 14
____ __
20-1. Delay line mode
DELAYLINE
This mode can be SAILishly represented this way:
Page 61
LRNSAM Delay Units DRAFT
for INDEX←BASE_ADDRESS step 1 until BASE_ADDRESS + DELAY_LENGTH do
begin
Result←TERM_0 + COEFF0*TERM_1;
TERM_0←<delay memory data received>[INDEX];
TERM_1←A_IN + COEFF1*<delay memory data received>[INDEX];
<delay memory data sent>[INDEX]←TERM_1;
end;
Please note, that the delay arrays are not cleared when initiallized, and
therefore will return junk on their first pass. Also, it requires an additional
three passes to return data from the delay unit, so the real turn around time is
DELAY_LENGTH + 3.
20-2. Table lookup mode
TABLE_LOOKUP
The data word received from the modifier is shifted to the right by DELAY_LENGTH
bits and then used to address the memory area assigned to the unit. In table
lookup mode, the length of blocks of memory are always powers of two. So the
address of any element in that block can be specified by a word whose length is
that power (e.g., a block of 64 words takes a 6 bit address). But the size of
the word containing the address which is passed by the modifier to the delay
unit is 20 bits, and the address is left adjusted in it (in case you needed the
full 20 bit address). So the delay unit generates the correct address by right
shifting until the address is right adjusted in the word, whereupon it becomes a
valid address. The number of shifts necessary is stored in DELAY_LENGTH in this
mode (whereas in DELAY_LINE mode it really means the delay length). The word in
the addressed memory location is returned to the modifier three passes later.
20-3. Table lookup - rounded
ROUND_TABLE_LOOKUP
Data received is shifted right, rounded then used to address delay memory. This
is useful when employing a block of delay memory to store things like sound-
pressure functions or any table with critical values as it gives you 3dB better
resolution than straight table lookup. The difference is like that between a
digital oscillator which looks up its table by truncating the address after
calculating the increment (which is essentially what TABLE_LOOKUP does) and one
Page 62
LRNSAM Delay Units DRAFT
which rounds first. The latter has 3dB better resolution. (1) The one
potential bug is if the address is rounded up off the last word of the table.
The two options in designing the machine were to wrap around like MUSCMP does,
or to allow the overflow. The latter was implimented for the reason that, if
you were doing a square root table, for instance, wraping around wouldn't give
you any kind of a right answer. So you must sanitize any overflow by sticking
some appropriate value in your table in at the n+1th. location if you use this
mode.
_______________________
() The next more accurate way to go is to interpolate between the two adjacent
values in the table, which is what the ZOSCIL oscillator does in MUSCMP.
Page 63
LRNSAM DRAFT
Section 21
_______ __
Calling the box
_______ ___ ___
The lowest level of access (short of writing your own assembler) is to set up a
SAIL program which requires the source file LOWER.DEF[SAM,MUS], and the load
module LOWER.REL[SAM,MUS]. LOWER.REL contains the SAIL procedures described
below which handle the set up of data paths in the box and the passing of
parameters. LOWER.DEF contains the macro definitions of all the terms such as
ANGLE and TERM_1 that are in the tables of this document, plus a slew of other
macros for various obscure system hacks. This is also where the arrays are
declared for the unit generators and sum memory, and where the procedures in
LOWER.REL are formally called.
The procedures are:
21-1. GET
External simple integer procedure GET(integer id).
This procedure does a lookup on the processing element lists and returns the
number of the lowest numbered free element. For instance if tweet is an
integer, saying: tweet←GET(id_generator) will return the number of the first
free generator in tweet. Tweet then has a value which consists of flags that
specify a particular unit generator. Likewise: tweet←GET(id_modifier) returns
the number of the first free modifier, and tweet←GET(id_delay) returns the
number of the first free delay. The terms in the definition list below are
loaded into your program by LOWER.DEF and contain addresses in the assembler
such that when you call the assembler with one of these variables in a GET
command, the assembler is pointed to that part of itself which deals with the
clerical work of claiming that type of process element.
Definition Function
--------------------------------------------------------------------------------
ID_GENERATOR gets a generator.
ID_MODIFIER gets a modifier.
ID_DELAY gets a delay unit.
ID_SUM_MEMORY gets a sum memory location.
Fig. 15
____ __
This works fine for claiming processing elements, but falls short of adequate
Page 64
LRNSAM Calling the box DRAFT
for sum memory, since one typically wants to select one particular coordinate or
other. GET(ID_SUM_MEMORY) only returns the absolute address of the lowest
numbered free sum memory location regardless of coordinate. To get a specific
coordinate, one must add the sum memory id in with one of the id's on the
following table. For example GET(ID_SUM_MEMORY+THIS_MOD_PASS) returns the first
available this pass sum memory location.
THIS_MOD_PASS gets modifier this pass sum memory.
LAST_MOD_PASS gets modifier last pass sum memory.
LAST_GEN_PASS gets generator last pass sum memory.
THIS_GEN_PASS gets generator this pass sum memory.
Fig. 16
____ __
21-2. GIVE
External simple procedure GIVE(integer id).
This does the opposite of GET. That is, it frees a unit generator from its
associated variable. It also makes that unit generator unknown to the universe
until another GET is perpetrated. (However, you can't be assured of getting the
same process element back, since it always takes the lowest numbered available
element.) A sample command: GIVE(tweet). It is not necessary to always GIVE
back processing elements. All you are doing is clearing an identifier of its
association. However, if your program is a dynamic allocation routine where you
may be repatching the box on the fly, if you continued glomming processing
elements without releasing any you would soon have more processing elements
bound than you have processing ticks to run them with. You would soon drop out
of real time (how degrading!).
21-3. BIND
External simple integer procedure BIND(integer id, name, value).
This allows you to associate different elements in the box. It has three
functions: a) to associate input data with fields in processing elements, b)to
associate sum memory with input and output ports of processing elements, 3) to
set the running status of processing elements. For instance:
28
BIND(tweet, FREQUENCY, 440*2 /clock_rate)
Page 65
LRNSAM Calling the box DRAFT
sets the generator referenced by the bits in tweet to have a frequency of 440
28
(That is, it puts 440*2 /clock_rate in FREQUENCY (GJ)). This is an example of
using BIND to associate data with a processing element. BIND is also the
procedure to associate the output and input addresses of processing elements.
For instance, the following sequence will link two generators:
BIND(gen_sum,SUM_MEMORY,tweedle)
BIND(didi,FM,gen_sum)
This will give the address which is in location SUM_MEMORY in tweedle (which is
the address of the sum memory location where tweedle writes its output) to
location FM in didi. When didi reads that address it will get the deposited
output of tweedle. An example of setting the running status of a processing
element might be: BIND(fut,MODE,A_RUNNING). Valid constructs for integer <name>
are all the generator, modifier and delay line SAIL definitons discussed above,
such as FREQUENCY, RATE, EXPONENT, MODE, A_IN, COEFF1, etc.
21-4. SET_OUTPUT
External simple procedure SET_OUTPUT(integer sail_channel).
SET_OUTPUT lets the user pass the assembler command output not only to the box
but to any number of devices. You must use this to set up a path to the box,
just as you must open any device before doing I/O to it. But, you can also, for
instance, open the disk and enter a file, and pass the disk channel number to
the assembler with a SET_OUTPUT and the assembler will also pass the command
stream to the opened file, thereby giving you a record of it.
21-5. SET_CHANNEL
External simple procedure SET_CHANNEL(integer data_chan,sail_chan).
There are "data" channels between the main computer and the box as well as a
"command" channel. SET_CHANNEL allows the user to set a channel for data input
____
and output to the box. This is how the disk and the ADC (if implemented) would
get data into the box. Also, this is how data would be forwarded from the box
to the main computer. This is not used for the DACs, which have another hook
into the box (they would be used in conjunction with a generator set up with a
BIND command to run mode WRITE_DAC, see page 24). The main use of reading and
writing samples to and from the operating system would be to do manipulation of
sound files. However, this means you must wait for disk ops, and since the disk
is the current bottleneck on the system, it probably means that this will be
Page 66
LRNSAM Calling the box DRAFT
basically a non-real time mode. The following are the available device channel
definitions:
Definition Function
--------------------------------------------------------------------------------
DISK_OUT output to the disk.
DISK_IN input from the disk.
ADC_IN input from the adc. (not likely soon).
*_IN add your favorite device here.
Fig. 17
____ __
21-6. SET_PROCEDURE
External simple procedure SET_PROCEDURE(reference procedure <name>).
SET_PROCEDURE allows the user to choose a home grown procedure for error
recovery. LOWER.REL contains a slew of error traps. The error handler first
stops execution then, assuming you have declared an error procedure of your own
with SET_PROCEDURE, it passes control to your procedure and passes a SAIL type
string to it containing a thumbnail characterization of your problem. What you
probably want your error procedure to do is to save the current state of the
world, close files that were open and generally make a controlled retreat. You
DON'T want it to try to continue execution, since things aren't set up to do
that. At this stage of things, all errors are fatal. An instance:
simple procedure BOX_ERROR(reference string errmsg);
begin
print('11&"You have done the following no-no:"
"&errmsg&'15&'12);
close(input_file);
close(output_file);
release(input_file);
release(output_file);
usererr(0,0,"Fatal low level error...");
end;
SET_PROCEDURE(box_error);
21-7. DECODE
external simple integer procedure DECODE(integer unit).
Page 67
LRNSAM Calling the box DRAFT
When passed an id that has been GETed, DECODE returns the type of unit, numbered
as follows:
ID_GENERATOR 0
ID_MODIFIER 1
ID_DELAY 2
ID_SUM_MEMORY 3
21-8. RELATIVE
External simple integer procedure RELATIVE(integer unit).
When passed an id that has been GETed, RELATIVE returns the number of that unit
relative to the base address of that type unit.
21-9. SET_MODE
External simple procedure SET_MODE(integer name).
This procedure is used to set various states of the whole box, such as number of
ticks per pass, etc.
Code Definition Function
--------------------------------------------------------------------------------
CONO-A RESET_TICK_COUNTER to beginning of pass (if stopped).
CONO-A PERMIT_PROCESSING_TICKS
CONO-A INHIBIT_PROCESSING_TICKS only update ticks are executed.
CONO-A RESET master reset.
MISC WAIT_CLEAR start up all waiting process elements.
MISC PAUSE_CLEAR start up all pausing process elements.
MISC STOP (screeech...)
OPTIMIZE pack as many commands per word as possible
(possibly dangerous: see below).
NON_OPTIMIZE pack one command per word,
but maybe don't run so efficiently.
Fig. 18
____ __
Example: SET_MODE(PAUSE_CLEAR).
OPTIMIZE mode is potentially dangerous because in optimizing, some commands may
Page 68
LRNSAM Calling the box DRAFT
get rearranged in time in a possibly fatal way. Mark Kahrs supplies the
following example. Suppose you want to change several commands, like:
bind(i1,mode,5);
bind(i1,fm,37);
...
bind(i1,sum_memory,sum_outa);
Providing there are no dwells... (Because a dwell causes the packing tables to
be cleared), then the sum_memory change will occur RIGHT after the mode change,
i.e., slightly out of time sequence However, the chances of such an horrendous
thing happening are not too likely (unless you write a really long drone piece).
There will be few cases where the micro-ordering will be that crucial.
21-10. SET_FIELD
External simple procedure SET_FIELD(integer name,field).
Use this to set the quantities of the various things described below:
Page 69
LRNSAM Calling the box DRAFT
Id Definition Function
--------------------------------------------------------------------------------
CONO-A CONTROL_MODE called only for three definitions below.
C_START start the box.
C_TICK cause one tick.
C_STOP stop the box. This command is identical to
STOP above, except that this one is
used by the controlling
system for interupts.
TICKS PROCESSING_TICKS set how many processing
ticks per pass.
TICKS TOTAL_TICKS set how many of both kinds of
ticks per pass.
update ticks/pass = (total ticks
- processing ticks) / pass.
TIMER DWELL process no more update ticks until
pass counter = data.
TIMER SET_PASSES set pass counter to data.
TIMER CLEAR_PASSES_DWELL clear pass counter then
DWELL until pass
number equals argument.
SIZE_BUFFER controls size of command buffer between
the controlling program and the box.
Initialized to 1024.
SIZE_COMMANDS Sets how many commands will be sent
to the buffer at once.
Initialized to 64.
PACKING_MODE called only for three definitions below:
RIGHT_JUSTIFIED take low order 20 bits of source, pack
into low order end of box destination field.
LEFT_JUSTIFIED take low order 20 bits of source, pack
into high order end of box destination.
FULL_WORD low order 28 bits packed into destination word.
Fig. 19
____ __
Example:
SET_FIELD(PROCESSING_TICKS, 300);
SET_FIELD(DWELL, CLOCK_RATE*5);
SET_FIELD(PACKING_MODE, FULL_WORD);
SET_FIELD(CONTROL_MODE,C_START);
21-11. BIND_FIELD
External simple integer procedure BIND_FIELD(integer id, name, value, type).
Page 70
LRNSAM Calling the box DRAFT
This acts just like BIND above, but it has an additional field which allows you
to temporarily (for this call only) change the packing mode. For example,
BIND_FIELD(foo,frequency,left_justified)
will select that packing mode for passing frequency this time, then revert to
whatever it was before.
21-12. Size of command buffer
There is a problem posed by the size of the buffering and size of the command
block. The command buffer is only flushed (passed to the box) when it is full.
So, if the size of the buffer is much bigger than the buffer inside Sam, you run
the risk of command underrun since the box's internal buffer could run out of
commands before the next block is put out. If the buffer is small, there is
less likelihood of underrun up to a point. The catch here would be that you
will need more I/O operations from the main computer system, and it may (in its
infinite wisdom) decide not to give them to you when you need them. And since
you have only a small buffer full of commands, you may run out soon, again
resulting in underrun. Richard Almaniac would probably have something to say
about this. (Don't count your ticks until they're passed?)
21-13. LOAD_DELAY
External simple procedure LOAD_DELAY(from_address,to_address,count).
This stuffs arrays into delay memory for table lookups, etc. The data format is
that which is set by SET_FIELD, see above. Notice that delay memory locations
are not arbitrarily associated with specific delay units, hence any delay unit
is free to read any (contiguous) part of delay memory.
21-14. INITIALIZE
External simple procedure INITIALIZE.
Initializes the code (useful in case of a restart).
Page 71
LRNSAM Calling the box DRAFT
21-15. FLUSH
External simple procedure FLUSH.
FLUSH should be called before exiting the user program. It forces data left in
all buffers to be written out. Failure to do so results in missing words at the
end. So please, FLUSH when you're through... and wash your hands.
21-16. Processing element arrays
This next bit is a list of the arrays that are declared in LOWER.DEF[SAM,MUS].
They are modified SAIL type arrays. The purpose of these arrays is to keep tab
of the processing elements and memory locations in use. When a processing
element is in any mode other than inactive there will be a non-zero value in its
corresponding array location. Notice that there is one big array from 0:671 and
several smaller ones that add up to a total of 672. Actually, they overlap,
such that the location referenced by unit_generators[0] is the same as that
addressed by generators[0], and location unit_generators[256] is the same as
modifiers[0], etc. The point of including these arrays is so that you can write
your own procedures to do the book keeping for the process elements instead of
using GET and friends. Also notice that all_sum_memory[0] is the same as
gen_sum_memory[0], and all_sum_memory[128] is mod_sum_memory[0]. The arrays are
allocated as follows:
Page 72
LRNSAM Calling the box DRAFT
--------------------------------------------------------------------------------
External integer array unit_generators[0:671].
All the generators, modifiers
delay units and sum memory addresses are listed here
as to availability.
External integer array generators[0:255]. Just the generators.
External integer array modifiers[0:127]. Just the modifiers.
External integer array gen_sum_memory[0:127].
Just the generator sum memory.
External integer array mod_sum_memory[0:127].
Just the modifier sum memory.
External integer array gen_this_pass_sum_memory[0:63];
external integer array gen_last_pass_sum_memory[0:63];
external integer array mod_this_pass_sum_memory[0:63];
external integer array mod_last_pass_sum_memory[0:63];
Individual quadrants.
External integer array all_sum_memory[0:255]. Both sum memories.
External integer array delays[0:31]. Just delay units.
Fig. 20
____ __
21-17. Tick counters
The next two variables keep track of the current update tick, and the current
pass number.
external integer update_tick.
external integer pass.
21-18. Steps in calling the box
There are some important things to realize about the order in which modes and
fields are set in the box. Most obviously, you must set or clear all fields in
the box before you crank it up. Any processing chains must be started up in the
Page 73
LRNSAM Calling the box DRAFT
order that they reference each other. Also, if modifiers are reading from this
pass sum memory, the writing processing element must have done its thing before
the reading modifier (meaning the writing element must be lower numbered than
the reader). So a typical program will do the following in roughly this order:
-Require LOWER.DEF source_file and LOWER.REL load_module,
-Do a SET_OUTPUT to pass a command channel to the assembler,
-GET the desired processing elements,
-Including generators to output samples to the DAC, if desired
-Calculate the number of ticks needed, pass that with SET_FIELD
to the assembler,
-Set the corresponding sample rate, and magic numbers,
-Set all unused processing element fields to reasonable values
with BIND,
-Set any unused FM ports in generators or A_IN type ports in
modifiers to some unused (hence 0) sum memory location
with GET and BIND,
-Set all used fields to their desired values with BIND,
-Connect processing elements to sum memory
(and hence to each other),
-Start the processing elements in order of reference
(i.e., start the generator passing samples
to the DAC last),
-DWELL until the event requires more update ticks,
-or if it's finished, GIVE back the used process elements
and sum memory locations when no longer needed so other
processors can get high-ordered time slots, keeping you in
real time.
-lastly, FLUSH!
Page 74
LRNSAM DRAFT
Appendix 1
________ _
An introduction to pipelining
__ ____________ __ __________
There are a number of ways to accomplish digital sound synthesis. If we used a
general purpose computer to do the processing for us, depending on which kind we
used and how busy it was otherwise, we might be able to accomplish our goal, but
there would be lots of wasted motion. General purpose computers are meant to be
able to handle a wide variety of kinds of jobs. Consequently, there is lots of
extra baggage that they carry along to allow this flexibility. We, however,
have a rather specific job to do: most signal processing jobs involve applying a
long series of data to a relatively fixed complex calculation. So the first
thing to do if we were designing a computer for our purposes is to dump as much
of the overhead and waste motion as possible (however, it would be nice if the
device we were to build were controlled by a large general purpose computer so
that we could have as flexible a way as possible to run it).
Let us take the following equation as an example of a calculation we might want
to do with our machine. It produces a cosine wave when fed lots of increasing
values of I,
Y ← A*SIN(6.28138*(I/512) + π/2). Equ. 16
Our calculator will only need to be able to add, to multiply and divide and to
look up values in a sine table. It would do the calculations in stages, just as
we would by hand: it would form intermediate results and then combine them and
put out the result. Such a machine, without the overhead of a general computer,
could be made to be very fast.
But this still has some problems. What if the formula we want were still too
time consuming to calculate at the rate of speed we want? What if we wanted
lots of these formulas to be running simultaneously and added together? How do
we expand this basic process but still keep the average computation time down?
If we just got lots of these processors and fed each one a number we would cut
the average compute time by 1/<the number of extra processors>. But we'd need
some fancy system of passing the numbers out and collecting the results.
A better approach is to realize that, since the calculation is broken down by
the processor into intermediate steps, it might be possible to have one
processor working on several values of I at once. To see how this can happen,
we must know the difference between block structure and pipelining. We have
been unconsciously treating the processing of the formula above in block
structure. In this mode, we take a number for I, subject it to all the
Page 75
LRNSAM An introduction to pipelining DRAFT
intermediate computations and wait for a final value for Y to pop out before we
take another value for I. But what if we did it this way:
Lets make an "assembly line" out of the process. A belt carries in the values
of I, lets say we have 5 values. The workers along the belt are the divider,
the multiplier, the adder and the sine-table-looker-upper, standing in the order
that we would normally solve the equation. Here comes the first I. The divider
picks up his number cruncher and divides it by 512. Meanwhile the rest of the
guys multiply, add and sine-table-lookup whatever junk the janitor left on the
belt last night, maybe his lunch or something. They all place their results
down on the belt, and it is carried along to the next one. The divider's number
is now in front of the guy who multiplies by 2π. Meanwhile, THE NEXT VALUE OF I
comes down the chute towards the divider! Notice we don't have a valid value
for Y at the other end yet. But if we continue, we will. So, the divider and
the 2π multiplier get to work doing their thing, meanwhile, the rest of the guys
continue playing with the janitor's lunch as it moves past them on the belt,
rolling it into little balls and throwing it at each other. They set down their
results, and now the adder gets the intermediate value of what was the first I,
the 2π multiplier gets the next, and the divider gets yet another new I. On the
next shift, the sine-table-looker-upper does his thing, and the value for the
first I is through the process, transformed into Y. Behind it are the other
intermediate values. So we need to crank the whole process three more times to
get the 5th Y value through the line. But notice, during these next three
passes, we get a valid Y value every time the assembly line turns around once.
That means we get a good result in the time of only one intermediate step,
whereas in the block process, we would have got only one result for every five
intermediate steps. This is a nice increase in average computing time. It's
called pipelining.
Lets look at some of the implications of this process. First of all, all the
inner operations are similar in that they constitute the picking up of a number,
the operation on it, and replacing it. We'll call this basic task a pass.
Furthermore, it should be obvious that the system cannot work any faster than
the slowest processor. The minimum execution time of a pass then is the time it
takes for the slowest computing element. So the accomplishment of one
intermediate result consists of a pass.
Notice that the processor must be primed like a pump., but once it is, it
produces one good value of output every pass. In general, if the pipeline is N
elements long, we must add N passes to the number of passes it would take to
send all the numbers through the pipeline.
Notice also we must throw away the first N samples we get of the output. In the
assembly line above, the processor was four units long, and we had five values
of I to calculate, so it took nine passes to accomplish the task, and we threw
away the first four values of the output since they were undefined (unless you
knew the janitor).
Page 76
LRNSAM DRAFT
Appendix 2
________ _
Time division multiplexing
____ ________ ____________
The Samson box is extensively pipelined. Just how extensively we will soon see.
Two of the essential ingredients in the box are called processing elements. One
of them is called a modifier, the other is called a generator. The modifier
does various flavors of arithmetic and logic testing, while the generator does a
fancier version of our example formula, but with lots of bells and whistles.
Pete Samson claims there are 256 generators and 128 modifiers in his box. In
fact, there's only one nest of electronics for the generators and one for the
modifiers. What really goes on is that there are 256 sets of parameters for the
generator and 128 sets of parameters for the modifier, and the processing
elements zap from one set to the next at a truely blinding rate of speed. This
is called time division multiplexing.
To get the full picture, reload your mental image of the example of pipelining
above. Think of it maybe this way:
I[1] I[2] I[3] I[4] I[5] * * * *
__↓__
| / |
| * |
| + |
|sin|
--↓--
* * * * Y[1] Y[2] Y[3] Y[4] Y[5]
Fig. 21
____ __
Each step the box takes from left to right accomplishes one pass, and on the
fifth pass, we start geting valid Y values. Now imagine the process if we want
to deal with many streams of data. All we need do is interleave the seperate
streams. Input I[1] will come out as output X[1], J will come out as Y, and K
as Z.
Page 77
LRNSAM Time division multiplexing DRAFT
I[1] J[1] K[1] I[2] J[2] K[2] I[3] J[3] K[3] I[4] J[4] K[4]
__↓__
| / |
| * |
| + |
|sin|
--↓--
* * * * X[1] Y[1] Z[1] X[2] Y[2] Z[2] X[3] Y[3] Z[3]...
Fig. 22
____ __
Before analyzing this, let's redefine our terms. What we were calling a pass,
____
lets now call a tick. So a tick is the act of moving the processor, sucking up
____
a number and spitting out another. Now let's redefine "pass" to mean that point
where we have produced one valid output for all of the streams. (Don't worry,
we'll stick to this definition. The term pass can be used in simple situations
like our first example, but from here on we reserve the word pass to refer to
the period culumnating in the output of samples.) So, where is the end of the
first pass? Right!, at Z[1], after seven ticks. Where is the end of the next
pass? Right again, at Z[2], after three ticks. From here on, a pass consists
of three ticks only. Since one pass equals one valid output, if we have five
each of I, J and K, it will take five passes of three ticks per pass to get them
all through PLUS four ticks to prime the processor for a grand total of 19
ticks.
We can deduce from this that the number of ticks per pass is equal to the number
of parallel data streams, also that the total number of ticks is equal to:
<# of streams>*<length of longest stream> + <length of pipeline>. Equ. 17
Remember that a pass consists of all the processing necessary to produce one
complete set of output data, that is, one sample. The tick is, therefore, a
subspecie of the pass. The tick is the smallest unit of time our process can
use and the time it takes is the time of the slowest process.
Page 78
LRNSAM DRAFT
Appendix 3
________ _
Basic number theory
_____ ______ ______
Trying to make representations between different number systems is somewhat the
same problem as finding suitable translations for unidiomatic foreign terms, we
have to agree on a meaning.
Modern computers represent numbers in a binary system. Briefly what a binary
number consists of in such a machine is a group of electronic devices each of
which can be "on" or "off", depending on the number being represented. Each
device stands for a particular weight or magnitude, and the number represented
by the set of devices is the weighted sum of the ones that are "on". Thus, if
we let three printing columns represent three such devices and we wanted to
represent the number 3 we would do this;:
weight: 4 2 1
state: 0 1 1
Fig. 23
____ __
where a 1 means "on" and 0 means "off". The word binary refers to the fact that
the devices have only these two states. The general expression for such a
number is
m 2 1 0 -1 -2 -n
b 2 +...b +b 2 +b 2 ∧ b 2 +b 2 +...b 2
m 2 1 0 -1 -2 -n
where the "∧" is the "binary point" (think of decimal point). The b term is the
state, m is the largest weight, n the smallest. The largest unsigned integer we
m
could represent would be 2 -1 where m is the number of places, or "bits" left of
the binary point. In Fig. 23, 7 is the largest number we could represent,
starting from zero.
For representing the domain of positive and negative numbers there are three
basic systems used: sign-magnitude, one's complement and two's compliment. In
the sign-magnitude system the leftmost bit, called the most significant bit
(MSB), or high order bit, is used to denote the sign of the number, zero being
Page 79
LRNSAM Basic number theory DRAFT
taken as positive, one as negative. So the highest integer magnitude that can
m-1
be represented is 2 -1.
One's complement and two's complement do things a little differently. Here the
negative numbers are expressed as the logical complement of their positive
counterpart. Complementation is done by changing all the zeros to ones and vice
n
versa. For one's complement the formula for the negative equivalent is 2 -x
where n is the number of bits and x is the number to be complemented. So the
one's complement of 001 is 110, which we represent in decimal as +1 and -1
n n
respectively. For two's complement the formula is (2 -1)-x or (2 -x)-1. For
example, to form the two's complement of 010, subtract one = 001, and complement
= 110. Or, equivalently, complement 010 = 101 and add one to the least
___
significant bit (or LSB) = 110. To show this graphicly:
binary two's one's sign-magnitude
011 +3 +3 +3
010 +2 +2 +2
001 +1 +1 +1
000 +0 +0 +0
111 -1 -0 -3
110 -2 -1 -2
101 -3 -2 -1
100 -4 -3 -0
Notice that the one's complement and sign-magnitude have two representations for
zero, whereas the two's complement has one zero and it is taken as positive.
Two's complement also has one negative number greater than its largest positive
number. This number has no complement. Notice that the two's and one's
complement system "wrap around" from the greatest positive number to the
greatest negative number when the greatest positive number is incremented
whereas the sign-magnitude system goes to zero again.
Now the Samson box uses two systems, the two's complement for signed numbers and
plain binary for unsigned numbers. Two's complement offers some advantages to
the computer designer and is nearly universal (exept for certain CDC machines!).
So far so good, now what if we wanted to represent fractional numbers instead of
integers? Well, for example, using the unsigned system, and expanding to four
bits it might look something like this:
Page 80
LRNSAM Basic number theory DRAFT
binary fractional decimal
1111 nearly 1.0 15
1110 14
1101 13
1100 .75 12
1011 nearly .75 11
1010 10
1001 9
1000 .5 8
0111 nearly .5 7
0110 6
0101 5
0100 .25 4
0011 nearly .25 3
0010 2
0001 1
0000 .0 0
n
The fractional value is then the <decimal value>/2 . The numbers just below the
the ones that have only one bit on (.5, .25, etc.) are often taken as being an
equivilent for the ones with only one bit, which is why I say they are, for
example, nearly .5, etc.
The two's complement version of this is:
binry fractioal decimal
0111 nearly 1.0 7
0110 6
0101 5
0100 .5 4
0011 nearly .5 3
0010 .25 2
0001 .125 1
0000 .0 0
1111 nearly .0 -1
1110 -.125 -2
1101 -.25 -3
1100 -.5 -4
1011 -5
1010 -6
1001 nearly -1.0 -7
1000 -1.0 -8
Here we have a -1.0, but only a +.999... or nearly +1.0. So it conforms with
the two's complement system which has one greater negative value than positive.
This has the formal name of two's complement fixed binary point notation. It is
Page 81
LRNSAM Basic number theory DRAFT
used everywhere in the box that a signed system is needed, most notably for
waveforms, which are representations of positive and negative fluxuations around
normal air pressure taken as zero. However, it is not used in simple counting
situations such as ANGLE or EXPONENT where the unsigned integer notation is
used.
Notice the sequence in the fractional notations that .5 = 0100..., .25 = 0010...
and .0125 = 0001... (where "..." stands for any additional bits to the right).
From this it can be seen that to multiply a number by a (positive) power of two,
N, requires only that it be shifted left N places. For division by a power of
2
two, shift right. This also holds for integer binary systems: ...0010 = 2 ,
4
...0100 = 2 , etc.
Be careful as to whether a number is signed or unsigned when combining them in
expressions as signed numbers are right shifted one bit compared to unsigned
numbers (which use the high order bit for magnitude, not sign).
This also brings up the general problem of adding words of different lengths.
Clearly, to get a valid result for an addition or subtraction, the weights of
the terms must match. If the words are of different lengths, the procedure is
to first normalize the two numbers (line up the weights), then extend the sign
bit of the shorter word out to the limit of the most significant bit, then add.
Page 82
LRNSAM DRAFT
Appendix 4
________ _
SAIL examples
____ ________
4-1. Generator example
What we want to do now is to set up some intermediate level routines out of the
low level that will do such basic things as set up DAC channels, sampling rate,
magic numbers, file output, etc. Then we'll call the procedures in the context
of a couple of abbreviated programs. These procedures will be specific to our
example programs; to make them truly general might obscure their didactic
purpose.
Our first "piece" will be to generate 6 seconds of a sine wave at A 440. For
you analog synthesizer cognoscenti, I've tried to write this example to make
sense in terms of the equivalent patching procedure.
The first procedure will set up a generator and a sum memory location as a
channel to a DAC. Assume from now on that ∂ stands for COMMENT.
integer procedure GET_OUTN(integer chan; reference integer gen_out);
∂ Gets DAC output generators;
begin
integer sm;
gen_out←get(id_generator); ∂ gen_out now has addr. of 1st.
free generator;
sm←get(id_sum_memory); ∂ sm gets addr. of 1st. free sum memory;
bind(gen_out,fm,sm); ∂ gen_out's fm input now reads contents
of sm;
bind(gen_out,sweep,chan); ∂ DAC addr. is put into the sweep slot;
bind(gen_out,mode,dac_write); ∂ Start up generator feeding DAC;
return(sm); ∂ this addr. will be used by generators
desiring output to DAC;
end;
This procedure sets the clock rate from the number of processing and update
ticks. Here srate is the sampling rate, frmag the magic number for scaling
frequencies, dfrmag is for SWEEP and gpmag is for RATE. These are all global
variables which are set here.
procedure SET_CLOCK(integer nptix,nutix);
begin
set_field(processing_ticks,nptix);
Page 83
LRNSAM SAIL examples DRAFT
set_field(total_ticks,nptix+nutix);
srate←1/(0.000000195*(nptix+nutix+9));
frmag←(1 lsh 28)/srate; ∂ frmag is the scaling
for FREQUENCY;
dfrmag←frmag/srate; ∂ dfrmag is the scaling for SWEEP;
gpmag←(1 lsh 24)/srate; ∂ gpmag is the scaling for RATE;
end;
This next procedure will set up a file to record the command stream. To do
this, get a system channel, open a disk file on it, then pass the channel number
to SET_OUTPUT, which will take any code and pass a copy of it to the file, as
well as to the box. Getchan and open are predeclared SAIL procedures which
return an available system channel and open it, respectively. Outstr prints a
message on a teletype, inchwl inputs characters to a string from a terminal
until it gets a line feed.
procedure FILOUT;
begin
string s;
com_chan←getchan;
open(com_chan,"dsk",'17,0,0,0,eof,fail);
do begin
outstr("output file = ");
enter(com_chan,s←inchwl,fail);
end until not fail;
set_output(com_chan); ∂ this passes the channel number
to the box assembler;
end;
The next two routines will setup and cleanup the box for this example. Zero is
a global integer which will contain the addr. of a sum memory location which we
will never write into, and which will hence always return zeros. (1) Outa is a
global integer which will have a DAC addr. set in GET_OUTN above. Gen_outa is
another global integer which will be the addr. of the generator in WRITE_DAC
mode. The BIND command makes the output of the unit generator producing the
sound go to outa, which is read by gen_outa and passed to the DAC.
procedure INIT;
begin
INITIALIZE; ∂ lower level initialization;
zero←GET(id_sum_memory);
SET_CLOCK(64,64); ∂ will produce a 38.4kHz sampling rate;
outa←GET_OUTN(1,gen_outa); ∂ a generator now desiring to pass
_______________________
(1) Since processing elements read sum memory whether we like it or not, if we
don't want junk coming into it we must set it's input to read from zero.
Page 84
LRNSAM SAIL examples DRAFT
samples to DAC 1 need only put the samples in sum memory in
location OUTA;
FILOUT;
end;
This next procedure will clear the box for this example. Giving back these
elements is not really necessary at this point since our "piece" ends here, but
in principle whenever you aren't using a processing element, GIVE it back so
subsequent GETs don't continually gobble up processing elements.
procedure FINISH;
begin
GIVE(gen_outa);
GIVE(outa);
FLUSH; ∂ don't forget to wash your hands;
close(com_chan);
release(com_chan);
end;
These next two procedures stuff values into a generator. We only want to
control the frequency and amplitude without anything else fancy in this example,
but we must zero the things we don't want. We'll break it down into two
procedures, one for frequency information, another for amplitude and enveloping.
procedure SET_GEN(integer ug; real freq);
begin
BIND(ug,sweep,0); ∂ set up oscilator params., this one for
sweep rate = 0;
BIND(ug,angle,0); ∂ initial angle = 0, so we get a sine wave.
Had it been (2↑20)/4, it would
have been a cosine wave, since
it would start at 90 degrees;
BIND(ug,fm,zero); ∂ the FM term is always read,
so if not wanted point it into
sum memory where it will only
get zeros;
BIND(ug,scale,0); ∂ scale must be set to zero when not using
sum of cosines mode;
BIND(ug,frequency,frmag*freq); ∂ set frequency;
BIND(ug,sum_memory,outa); ∂ pass it's output to outa;
end;
Page 85
LRNSAM SAIL examples DRAFT
This next will setup or update a generator's envelope side, and start the
generator. We'll use an envelope shape that has a positive going attack from
zero amplitude to 1. Gpmag*duration gives an increment size that will step
through these values in that time. The steady state will be flat, so the
increment will be 0. The decay goes from 1 to zero, so the increment will be
gpmag*-duration. Another way to do this would be to use the C_RUNNING generator
mode. Envstate will be tested for envelope status: if envstate < 0 then setup
an attack, if = 0 then setup a steady state, else setup decay;
procedure SET_ENV_AND_DWELL(integer ug; real duration);
begin
own integer envstate;
if envstate < 0 then
begin "ATTACK"
BIND(ug,exponent,0); ∂ set up initial point in ramp;
BIND(ug,asymptote,0); ∂ set up envelope d.c. offset;
BIND(ug,rate,gpmag*duration); ∂ set attack ramp increment;
envstate←0;
end "ATTACK"
else if envstate = 0 then
begin "STEADYSTATE"
BIND(ug,rate,gpmag*0); ∂ ramp increment is zero during
steady state;
envstate←1;
end "STEADYSTATE"
else begin "DECAY"
BIND(ug,rate,gpmag*-duration); ∂ ramp increment is negative
during decay;
envstate←-1;
end "DECAY";
SET_FIELD(dwell,duration*srate); ∂ set dwell time;
end;
Now for the body of the program. Assume the above procedures are put in before
the first executable statement.
BEGIN "SIMP"
define ∂ = "COMMENT";
∂ get the source file definitions;
Require "LOWER.DEF[SAM,MUS]" source_file;
Require "LOWER.REL[SAM,MUS]" load_module;
integer
simp, ∂ will contain the address of the sound producing
generator;
zero, ∂ will be given a sum memory addr. not written into;
Page 86
LRNSAM SAIL examples DRAFT
gen_outa, ∂ will be the generator in WRITE_DAC mode;
outa, ∂ will contain a sum memory address connected to a
generator in WRITE_DAC mode;
srate, ∂ will be the sampling rate;
frmag, ∂ magic number for FREQUENCY;
dfrmag, ∂ magic number for SWEEP;
gpmag, ∂ magic number for mumble;
com_chan,brk,eof,fail; ∂ these are for SAIL i/o ;
∂ the above procedures go here;
∂ here begins the program initialization;
simp←GET(id_generator); ∂ gloms a generator and returns its address;
INIT;
SET_GEN(simp,440); ∂ setup arguements to simp;
SET_ENV_AND_DWELL(simp,2); ∂ now we get the attack;
∂ here ends the initialization. All parameters must be in place before
any run modes are passed;
BIND(simp,mode,a_running+lplusq+cosine);∂ beat me daddy, 8 to the bar...;
SET_ENV_AND_DWELL(simp,2); ∂ now the steady state;
SET_ENV_AND_DWELL(simp,2); ∂ now we get the decay;
GIVE(simp);
FINISH;
END "SIMP";
4-2. Modifier example
The next example will be a problem using modifiers to produce a scaled range of
random numbers which can be used to drive generators or other modifiers. Then
I'll show how to use a generator and scaling modifier to produce random ranges
of time durations.
First then, say we wanted a random number scaled between a certain range to
control the frequency of a generator so as to get one random pitch per note. To
do so we would, for instance, set up a modifier in TR_U_NOISE mode and apply its
output to the generator. We want TR_U_NOISE if we want a pitch selection to
stay over many passes, i.e. the length of the note. The modifier is triggered
by a non-zero value in some sum memory location that it reads. Then we turn it
off on the next pass before the modifier can trigger on it again.
Page 87
LRNSAM SAIL examples DRAFT
Since the generator's only input inside the box comes from its FM port, that's
where we will read it in. The first problem we face is that the number, call it
x, coming in from the modifier has a range of -1 to +1, but we want a number, Y,
that has a range between frequencies a and b. The general formula for scaling
is
x - L0
Y = -------*(a - b) + b
L1 - L0
where L1 is the upper bound and L0 is the lower (in this case 1 and -1).
Substituting for the L terms this becomes
x+1
= ---(a-b) + b.
2
But we don't have a modifier that can do all of this at once. Recall, that the
algorithm for TR_U_NOISE is:
Result ← COEFF0*TERM_1 + TERM_0. If B_IN*COEFF1 ≠ 0 then TERM_1←result.
So we have to do a little juggling:
x+1
= ---(a-b) + b
2
a-b a-b
= x*--- + --- + b
2 2
a-b a+b
= x*--- + ---
2 2
We can precalculate (a-b)/2 and give it to COEFF0 of the modifier where it would
be multiplied by x stored in TERM1. That takes care of the first term. That
leaves us with the second term (a+b)/2. This can be inserted in the FREQUENCY
port of the scaled generator on an update tick. When the FM port of this
generator picks up the result of the partial scaling from the modifier, it will
add the contents of FREQUENCY to it, and we get the whole expression.
Some technical problems remain. We must expand our earlier procedures somewhat
and invent some new ones. First, SET_GEN now needs to be able to set up an FM
read address from sum memory. We need a procedure called SET_MOD to do a
comparable thing for modifiers. Furthermore we now need to create a way to
Page 88
LRNSAM SAIL examples DRAFT
trigger the TR_U_NOISE modifier. Of the uncountable ways to stuff numbers into
sum memory, we'll crank up another generator in PULSE_TRAIN mode with the same
period as the note length. PULSE_TRAIN mode produces a +.5 whenever ANGLE
overflows. We want it to overflow on the first pass, so we'll set angle to
'3777777 (20 bits full of 1's). The first time it gets added in with FREQUENCY,
it will overflow, producing one pulse lasting one pass. If we want more than
one random pitch per note, just decrease the period of the pulsing generator.
begin "RAND"
define ∂ = "COMMENT";
Require "LOWER.DEF[SAM,MUS]" source_file; ∂ get the source file definitions;
Require "LOWER.REL[BOX,MWK]" load_module; ∂ get the source file definitions;
string s;
real a,b;
integer
simp, ∂ will contain the address of the sound producing
generator;
zero, ∂ will be given a sum memory addr. not written into;
gen_outa, ∂ will be the generator in WRITE_DAC mode;
outa, ∂ will contain a sum memory address connected to a
generator in WRITE_DAC mode;
srate, ∂ will be the sampling rate;
frmag, ∂ magic number for FREQUENCY;
dfrmag, ∂ magic number for SWEEP;
gpmag, ∂ magic number for mumble;
com_chan,brk,eof,fail; ∂ these are for SAIL i/o ;
integer loctrigger, locnoise, noise, trigger, notelength,rand;
procedure FINISH;
begin
GIVE(gen_outa);
GIVE(outa);
FLUSH; ∂ don't forget to wash your hands;
close(com_chan);
release(com_chan);
end;
procedure SET_ENV_AND_DWELL(integer ug; real duration);
begin
own integer envstate;
if envstate < 0 then
begin "ATTACK"
BIND(ug,exponent,0); ∂ set up initial point in ramp;
BIND(ug,asymptote,0); ∂ set up envelope d.c. offset;
Page 89
LRNSAM SAIL examples DRAFT
BIND(ug,rate,gpmag*duration); ∂ set attack ramp increment;
envstate←0;
end "ATTACK"
else if envstate = 0 then
begin "STEADYSTATE"
BIND(ug,rate,gpmag*0); ∂ ramp increment is zero during
steady state;
envstate←1;
end "STEADYSTATE"
else begin "DECAY"
BIND(ug,rate,gpmag*-duration); ∂ ramp increment is negative
during decay;
envstate←-1;
end "DECAY";
SET_FIELD(dwell,duration*srate); ∂ set dwell time;
end;
integer procedure GET_OUTN(integer chan; reference integer gen_out);
∂ Gets DAC output generators;
begin
integer sm;
gen_out←get(id_generator); ∂ gen_out now has addr. of 1st.
free generator;
sm←get(id_sum_memory+last_gen_pass);
∂ sm gets addr. of 1st. free sum memory;
bind(gen_out,fm,sm); ∂ gen_out's fm input now reads contents
of sm;
bind(gen_out,sweep,chan); ∂ DAC addr. is put into the sweep slot;
bind(gen_out,mode,dac_write); ∂ Start up generator feeding DAC;
return(sm); ∂ this addr. will be used by generators
desiring output to DAC;
end;
procedure FILOUT;
begin
string s;
com_chan←getchan;
open(com_chan,"dsk",'17,0,0,0,eof,fail);
do begin
outstr("output file = ");
enter(com_chan,s←inchwl,fail);
end until not fail;
set_output(com_chan); ∂ this passes the channel number
to the box assembler;
end;
procedure SET_CLOCK(integer nptix,nutix);
begin
Page 90
LRNSAM SAIL examples DRAFT
set_field(processing_ticks,nptix);
set_field(total_ticks,nptix+nutix);
srate←1/(0.000000195*(nptix+nutix+9));
frmag←(1 lsh 28)/srate; ∂ frmag is the scaling
for FREQUENCY;
dfrmag←frmag/srate; ∂ dfrmag is the scaling for SWEEP;
gpmag←(1 lsh 24)/srate; ∂ gpmag is the scaling for RATE;
end;
procedure INIT;
begin
INITIALIZE; ∂ lower level initialization;
zero←GET(id_sum_memory+last_gen_pass);
SET_CLOCK(64,64);
outa←GET_OUTN(1,gen_outa);
locnoise←GET(id_sum_memory+this_gen_pass);
∂ the output of the modifier must
be in generator sum memory for the
generator to be able to read it;
loctrigger←GET(id_sum_memory+this_mod_pass);
∂ the modifier in TR_U_NOISE has to have some place to
test to select its next random number.;
FILOUT;
end;
procedure SET_MOD(integer ug, outloc, a_read, b_read; real c0, c1,trm0, trm1);
begin
BIND(ug,sum_memory,outloc);
BIND(ug,a_scale,1);
BIND(ug,b_scale,1);
BIND(ug,term_0,trm0);
BIND(ug,term_1,trm1);
BIND(ug,coeff0,c0);
BIND(ug,coeff1,c1);
BIND(ug,a_in,a_read);
BIND(ug,b_in,b_read);
end;
procedure SET_GEN(integer ug; real frq; integer fm_read, set_angle);
begin
BIND(ug,sweep,0); ∂ set up oscilator params., this one for
sweep rate = 0;
BIND(ug,angle,set_angle); ∂ initial angle = 0, so we get a
sine wave. Had it been (2↑20)/4, it would
have been a cosine wave, since
it would start at 90 degrees;
Page 91
LRNSAM SAIL examples DRAFT
BIND(ug,fm,fm_read); ∂ now we want FM to read from the
signal coming from the scaling
modifier;
BIND(ug,scale,0); ∂ scale must be set to zero when not using
sum of cosines mode;
BIND(ug,frequency,frq);
BIND(ug,sum_memory,outa); ∂ pass it's output to outa;
end;
simp←GET(id_generator); ∂ gloms a generator and returns its address;
trigger←GET(id_generator);
noise←GET(id_modifier); ∂ get modifier;
INIT;
rand←ran(0); ∂ A predeclared SAIL random number getter, set to zero always
takes its seed as the date+TimeOfDay;
print("Note length = ");
notelength←realscan(s←inchwl,brk);
print("Upper frequency bound = ");
a←realscan(s←inchwl,brk);
print("Lower frequency bound = ");
b←realscan(s←inchwl,brk);
∂ here we setup the triggering generator;
SET_GEN(trigger,frmag/notelength,zero,'3777777);
∂ we want to set ANGLE so it will overflow;
∂ in addition, we want to stop the enveloping in the "on" position so as
to get a non-zero pulse, as follows;
BIND(trigger,exponent,1); ∂ set to maximum to pass oscillator pulse as is;
BIND(trigger,asymptote,0); ∂ no dc offset else we'd get constant new notes;
BIND(trigger,rate,0); ∂ set attack ramp increment to zero so it
will stay at maximum;
SET_MOD(noise, locnoise,loctrigger,zero,frmag*((a-b)/2),1, 0, rand);
∂ here's where we stick the first term of the frequency scaling;
SET_GEN(simp,frmag*((a-b)/2), locnoise,0);
∂ here's where we stick the second term of the frequency scaling;
SET_ENV_AND_DWELL(simp,notelength/3); ∂ now we get the attack, set
here arbitrarily to be 1/3 of the note's length;
∂ here ends the initialization. All parameters must be in place before
any run modes are passed;
BIND(trigger,mode,pulse_train);
BIND(noise,mode,tr_u_noise);
BIND(simp,mode,a_running+lplusq+cosine);
Page 92
LRNSAM SAIL examples DRAFT
SET_ENV_AND_DWELL(simp,notelength/3); ∂ now the steady state;
SET_ENV_AND_DWELL(simp,notelength/3); ∂ now we get the decay;
GIVE(simp);
GIVE(noise);
GIVE(trigger);
FINISH;
end "RAND";
4-3. Delay line example
The following is supplied as a terse example of using a delay line for table
lookups. It just shows how to connect delay memory to a delay line to a
modifier, and how to fill the delay memory. Left out are all the settings of
the modifier parameters and read-write addresses to sum memory and the interface
to either the DACs or a file.
begin "delay"
require "lower.def" source_file;
require "lower.rel" load_module;
integer delay_port,mod;
define ∂ = "comment ";
define len = "10";
define loc = "1033"; ∂ some random place in delay memory;
preload_with 0,1,2,3,4,5,6,7,8,9;
safe integer array table[0:len-1];
load_delay(table,loc,len); ∂ stuff array TABLE into someplace in delay memory;
mod ← get(id_modifier);
delay_port ← get(id_delay);
bind(delay_port,mode,table_lookup); ∂ set to table lookup mode;
bind(delay_port,base_address,loc); ∂ this delay starts at loc;
bind(delay_port,delay_length,len); ∂ len lookups;
bind(delay_port,index,len/2); ∂ set index to some location or other;
bind(mod,invoke_delay_unit,delay_port); ∂ delay_port is bound to mod;
∂ we assume great things happen here;
∂ now lets change the function of the delay unit to delay line mode;
bind(delay_port,mode,delayline); ∂ We can now use this as a delay line;
∂ More great things happen here;
give(delay_port); ∂ OK, give it back!;
give(mod);
Page 93
LRNSAM SAIL examples DRAFT
end "delay";
4-4. Writing your own procedures
The following is an example of how you could write your own procedures to handle
box setup. This routine gets N consecutively numbered generators. It looks up
array UNIT_GENERATORS which contains a location for all the elements in the box
and whether they are in use or not. It requires a global variable NPTIX which
would contain the number of processing ticks available on this pass, and hence
the number of processing elements that can run. It also needs to know what kind
of processing element you want, specifying it by saying ID_GENERATOR,
ID_MODIFIER , ID_DELAY or ID_SUM_MEMORY which are defined in LOWER.DEF to be 0,
1, 2 and 3 respectively. The routine returns the number of the first unit in
the sequence. If there are not N units in a row, it returns -1.
integer procedure GET_N_UNITS(integer TYPE, NLOCS);
begin "GET_N"
integer I,J,BASE,LIMIT,NEED_N_TIX;
define ⊂ = "COMMENT";
boolean GOTIT;
⊂ BASE contains the base address of the type
of element in UNIT_GENERATORS;
base← case TYPE of(0,256,384,640);
⊂ now figure out how many processing ticks the desired
number of processing elements would take;
case type of
begin
[0] need_n_tix←nptix; ⊂ generators take only one
processing tick;
[1] need_n_tix←nptix/2; ⊂ it takes two processing
ticks per modifier;
[2] need_n_tix←nptix; ⊂ delay memory takes no
processing ticks;
[3] need_n_tix←(nptix-6)/4 ⊂ this is the formula
for available delay cycles;
end;
⊂ LIMIT has maximum processing ticks for all of a
type of element to be running;
limit← case type of(256,256,0,134);
if nlocs > need_n_tix then
begin
outstr("You are too greedy");
return(0);
end;
Page 94
LRNSAM SAIL examples DRAFT
gotit←false;
for i←0 step 1 until NPTIX-NLOCS do
begin "ISTART"
if ¬UNIT_GENERATORS[i+base] then
begin "CHKIT"
for j←i+1 step 1 until i+NLOCS-1 do
if UNIT_GENERATORS[j+base] then
begin "NOGOOD"
i←j;
continue "ISTART";
end "NOGOOD";
gotit←true;
done "ISTART";
end "CHKIT";
end "ISTART";
if ¬gotit then return(-1);
for j←i step 1 until i+NLOCS-1 do
UNIT_GENERATORS[j+base]←true;
return(i);
end "GET_N";
Page 95
LRNSAM DRAFT
Appendix 5
________ _
Generator field and mode tables
_________ _____ ___ ____ ______
5-1. Generator parameters
ID SIZE DEFINITION FUNCTION
--------------------------------------------------------------------------------
GO 20 SWEEP oscillator frequency sweep rate
GJ 28 FREQUENCY oscillator frequency
GK 20 ANGLE oscillator angle
GN 11 NCOSINES number of cosines to be summed
GM 4 SCALE binary scale of cosine or sum of cosines
GP 20 RATE envelope rate of change
GQ 24 EXPONENT envelope current value
GL 12 ASYMPTOTE asymptote (DC offset of the envelope)
GSUM 6 SUM_MEMORY sum memory location into which
output is added
GFM 7 FM sum memory address from which frequency
modulation data is taken
GMODE 10 MODE generator mode, including run mode and
type of oscillator and enveolpe processing
(see list of modes on page 22).
OSC_MODE sets oscillator field of GMODE without
altering run mode or envelope mode.
ENVELOPE sets envelope field of GMODE without
altering run mode or oscillator mode.
5-2. Generator run modes
Definition Osc. run? Envelope run? Add to sum?
--------------------------------------------------------------------------------
G_INACTIVE no no no
G_PAUSE no no no
G_WAIT yes no no
x_RUNNING:
A_RUNNING yes yes yes
B_RUNNING yes yes yes
C_RUNNING yes yes yes
DATA_READ no yes yes
DATA_WRITE no no no
DAC_WRITE no no no
Page 96
LRNSAM Generator field and mode tables DRAFT
5-3. Oscillator modes
--------------------------------------------------------------------------------
SAWTOOTH sawtooth wave
SQUARE square wave
SUM_OF_COSINES sum of cosines
PULSE_TRAIN pulse train
COSINE sine(ANGLE),normal sine wave
mode (with fm).
COS_FM cosine(FREQUENCY + FM)
special sine table lookup mode.
5-4. Envelope modes
Definition Function
--------------------------------------------------------------------------------
LPLUSQ Add EXPONENT (GQ) to the offset constant
in ASYMPTOTE (GL).
LMINUSQ Subtract EXPONENT (GQ) from offset constant
in ASYMPTOTE(GL)
(-EXPONENT (GQ))
LEXPLUS Add ASYMPTOTE (GL) to 2 and scale.
(-EXPONENT (GQ))
LEXMINUS Subtract ASYMPTOTE (GL) to 2 and scale.
Page 97
LRNSAM DRAFT
Appendix 6
________ _
Modifier field and mode tables
________ _____ ___ ____ ______
6-1. Modifier parameters
Id Size Definition Function
--------------------------------------------------------------------------------
M0 30 COEFF0 coefficient
M1 30 COEFF1 other coefficient
L0 20 TERM_0 running term
L1 20 TERM_1 other running term
MIN 8 A_IN read address in sum memory
MRM 8 B_IN other read address
MSUM 7 SUM_MEMORY write address in
sum memory
ADD_SUM_MEMORY same as SUM_MEMORY.
REPLACE_SUM_MEMORY also has write addr.
but result replaces sum mem. value
MMODE 9 MODE run mode of modifier
and scale factor for the multiplies
A_SCALE scales COEFF1.
B_SCALE scales COEFF0.
Page 98
LRNSAM Modifier field and mode tables DRAFT
6-2. Modifier procedures
Definition Function
--------------------------------------------------------------------------------
M_INACTIVE inactive.
MIXING fixed binary point result of COEFF_0*A_IN + COEFF_1*B_IN.
INT_MIXING Integer result of COEFF_0*A_IN + COEFF_1*B_IN.
LATCH Result ← Term_1. If B_IN*COEFF_0 ≠ 0 then TERM_1←A_IN.
SIGNUM If A_IN*COEFF0 < B_IN*COEFF1 then result ← -1 else if
A_IN*COEFF0 = B_IN*COEFF1 then result ← 0 else if
A_IN*COEFF0 > B_IN*COEFF1 then result ← 1.
ZERO_CROSSING_PULSER
If (B_IN*COEFF0)*(L1*COEFF1) ≤ 0 then result ← - 1
else result ← 0. L1 ← B_IN*COEFF0.
MINIMUM The lesser of A_IN*COEFF0 or B_IN*COEFF1 is returned.
MAXIMUM The greater of A_IN*COEFF0 or B_IN*COEFF1 is returned.
AMPLITUDE_MODULATION
Result ← L1*COEFF1. L1 ← A_IN*((B_IN+1)/2).
FOUR_QUAD_MULTIPLY
Result ← TERM_1*COEFF1. TERM_1 ← A_IN*B_IN.
U_NOISE TERM_1 ← Result ← COEFF0*TERM_1 + TERM_0.
TR_U_NOISE Result ← COEFF0*TERM_1 + TERM_0.
If B_IN*COEFF1 ≠ 0 then TERM_1←result.
THRESHOLD If COEFF0*A_IN + TERM_0 < 0 then result ← 0, else
if ≥ 0 then result ← COEFF1*B_IN.
FILTERING:
ONE_POLE
ONE_ZERO
TWO_0POLES COEFF0 variable
TWO_1POLES COEFF1 variable
TWO_0ZEROS COEFF0 variable
TWO_1ZEROS coeff1 variable
Page 99
LRNSAM DRAFT
Appendix 7
________ _
Delay unit field and mode tables
_____ ____ _____ ___ ____ ______
Fields:
Id Definition Function
--------------------------------------------------------------------------------
P MODE Delay unit mode
Z DELAY_LENGTH Delay line unit length
SCALE or table address scale factor
Y INDEX Running index of the delay array
X BASE_ADDRESS Base address of delay array
Modes:
D_INACTIVE dead
DELAYLINE
TABLE_LOOKUP
ROUNDED_TABLE_LOOKUP
7-1. Delay units: timing
Delay units have their own formula: Delay memory cycles available per pass =
(<#_of_processing_ticks> - 6) / 4. Equ. 18
(If you are loading delay memory while doing processing, i.e., if you are
changing delay memory arrays from the main computer during execution of samples,
then the number of delay cycles is this number less however many cycles the main
computer may take to load the array. Each delay memory cycle used to load
arrays in this fashion will load one word. This method probably will not be
used much. The typical usage will be to load the arrays before cranking up the
box, so that the sound processing is not in competition with computer I/O.)
Page 100
LRNSAM DRAFT
Appendix 8
________ _
Reserved Words
________ _____
a_in dac_write
a_running data_read
a_scale data_write
adc decode
add_sum_memory delay
all_sum_memory delay_length
angle delayline
asymptote delays
b_in diagnostic_address
b_running dis_ticks
b_scale disk_in
base_address disk_out
bias_delay dmio
bias_end doinp
bias_generator dooutp
bias_modifier drio
bias_sum_memory dwell
bind dx
bind_field dx_load
c_running ena_ticks
c_start envelope
c_stop envlsb
c_tick envmask
clear_passes_dwell exp_just(x)
coeff0 exponent
coeff1 flush
coni_a fm
coni_b fn_maximum
coni_d fn_minimum
coniab fnlsb
cono_a fnmask
cono_b four_quad_multiply
control_mode freq_just(x)
cos_fm frequency
cosine full_word
d_data(x) function
d_inactive g_data(x)
d_number(x) g_inactive
d_op(x) g_number(x)
d_whole_data(x) g_op(x)
dac g_pause
Page 101
LRNSAM Reserved Words DRAFT
g_wait misc_stop(x)
gen_last_pass_sum_memory misc_wait_clear(x)
gen_this_pass_sum_memory mixing
generators mod_last_pass_sum_memory
get mod_this_pass_sum_memory
give mode
id_delay modifiers
id_generator ncosines
id_maximum no_asymptote(x)
id_modifier no_fm(x)
id_sum_memory no_gmode(x)
index no_gsum(x)
inhibit_processing_ticks no_in(x)
initialize no_l(x)
int_mixing no_m(x)
k_clear(x) no_mmode(x)
l0_clear(x) no_msum(x)
l1_clear(x) no_n(x)
latch no_ncosines(x)
left_justified no_rm(x)
lexpminus no_scale(x)
lexpplus non_optimize
lminusq notwopoles
load_delay notwozeroes
lounge o_angle
lplusq o_asymptote
m_data(x) o_base
m_inactive o_delay
m_just(x) o_dmode
m_number(x) o_exponent
m_op(x) o_fm
m_select(x) o_frequency
max_envelope o_gmode
max_oscillator o_gsum
maximum o_in
min_envelope o_index
min_oscillator o_l
minimum o_l_0
misc_data(x) o_l_1
misc_op(x) o_m
misc_pause_clear(x) o_m_0
Page 102
LRNSAM Reserved Words DRAFT
o_m_1 osclsb
o_mmode oscmask
o_msum p_a_scale(x)
o_ncosines p_angle(x)
o_rate p_asymptote(x)
o_rm p_b_scale(x)
o_scale p_cause(x)
o_size p_control(x)
o_sweep p_d_mode(x)
one_pole p_d_scale(x)
one_zero p_drb(x)
op_angle p_exponent(x)
op_asymptote p_fm(x)
op_base p_frequency(x)
op_delay p_g_envelope(x)
op_dmode p_g_osc_mode(x)
op_exponent p_g_run_mode(x)
op_fm p_gmode(x)
op_frequency p_gsum(x)
op_gmode p_in(x)
op_gsum p_int(x)
op_in p_l_0(x)
op_index p_l_1(x)
op_l p_l(x)
op_l_0 p_m_0(x)
op_l_1 p_m_1(x)
op_m p_m_function(x)
op_m_0 p_m(x)
op_m_1 p_mmode(x)
op_mmode p_mr(x)
op_msum p_msum(x)
op_ncosines p_ncosines(x)
op_rate p_pia(x)
op_rm p_rate(x)
op_scale p_re(x)
op_size p_rm(x)
op_sweep p_rtc(x)
op_ticks p_scale(x)
op_timer p_spt(x)
optimize p_sweep(x)
osc_mode packing_mode
Page 103
LRNSAM Reserved Words DRAFT
pass time_data(x)
pass_clear time_op(x)
pass_set tix_processing
pause_clear tix_total
permit_processing_ticks total_ticks
PIA tr_u_noise
processing_ticks ttl_load
pulse_train ttla
qflush ttlb
qlens two_0poles
rate two_0zeroes
relative two_1poles
replace_sum_memory two_1zeroes
reset u_noise
reset_tick_counter unit_generators
right_justified update_tick
round_table_lookup wait_clear
run_mode zero_crossing_pulser
runlsb
runmask
sawtooth
scale
set_channel
set_field
set_mode
set_output
set_passes
set_procedure
signum
size_buffer
size_commands
square
stop
sum_memory
sum_of_cosines
sweep
table_lookup
term_0
term_1
tick_data(x)
tick_op(x)
Page 104
LRNSAM DRAFT
Appendix 9
________ _
Error messages
_____ ________
Bind: Delay port used for sum memory
Bind: Identifier out of range
Bind: Invalid name for identifier type
Bind: Invalid sum memory location
Bind: name out of range
Get: Identifier type out of range
Give: identifier out of range
Set_channel: Zero channel
Set_field: Field index out of range
Set_mode: Mode index out of range
Warning: Extraneous bits on in generator bind
Warning: Extraneous bits on in modifier coefficient bind
Warning: No output channels!
Page 105
LRNSAM Index DRAFT
I N D E X
(References are to Page numbers)
A_IN 43, 45 COSINE 29, 31
A_RUNNING 23 CPU 13
A_SCALE 43
ADC 66 DAC 36, 66
ADD_SUM_MEMORY 44 DAC path 14
Addressing sum memory 17 DAC_WRITE 24
Amplitude modulation 48 data underrun 8
AMPLITUDE_MODULATION 48 DECODE 67
ANGLE 27, 37, 39 Definition 20, 34
Arithmetic overflow 23, 24, 46, 48 Definitions 2
array all_sum_memory 73 delay line 10
array delays 73 Delay line example 93
array gen_sum_memory 73 Delay line mode 61
array gen_this_pass_sum_memory 73 delay memory 2, 10, 11, 61
array generators 73 Delay unit 2, 11
array mod_sum_memory 73 Delay unit field and mode tables 100
array modifiers 73 Delay Units 61, 62, 63
array unit_generators 73 Delay units: timing 100
ASYMPTOTE 27, 32, 38 DELAYLINE 61
digital filtering theory 54
B_IN 43, 45 direct memory access 13
B_RUNNING 23 disk 66
B_SCALE 43 disk bandwidth 8
band limiting 31 DMA 13, 24
band-limited pulse train 30 Duration scaling 40
bandwidth 55 DWELL 14
base address 68
Basic number theory 79, 80, 81, 82 envelope 10, 19
BIND 65 Envelope modes 34, 35
BIND_FIELD 70 Envelope processing 32, 33
block structure 75 Error messages 105
exiting 72
C_RUNNING 24 EXPONENT 27, 32, 38
Chaining processing elements 16
clear all pause bits 22 Filtering algorithms52, 53, 54, 55,
clear all wait bits 23 56, 57
COEFF0 43 fixed binary point notation 45
COEFF1 43 flow of data 13, 14, 15
Coefficient terms 43 FLUSH 72
command buffer 71 FM 24, 27, 38
Command path 13 foldover 31
command underrun 15, 71 formant filter 55
COS_FM 30, 37 Four quadrant multiply 48
Page 106
LRNSAM Index DRAFT
FOUR_QUAD_MULTIPLY 48 Latch 46
Four-quadrant-multiply 48 left-shifting 43
fractional notation 45 LEXMINUS 35
Free mode 23 LEXPLUS 34
FREQUENCY 27, 37, 39 LMINUSQ 34
frequency response 54 LOAD_DELAY 71
Frequency scaling 39 low-pass filter 54
LOWER.DEF 64
G_PAUSE 22 LOWER.REL 64
G_WAIT 23, 24 LPLUSQ 34
generator 2, 77 LSH 43
Generator field and mode tables 96, 97
Generator parameters 20 M_INACTIVE 45
Generator run modes 22, 23, 24, 25 MAG 39
Generator tick time 5 Magic numbers 39, 40, 41
Generators 10, 19, 20, 21 Maximum 47
GET 64 Minimum 47
GFM 38 Mixing 45
GIVE 65 MODE 43
GJ 37 modifier 2, 77
GK 37 Modifier field and mode tables 98, 99
GL 38 Modifier parameters 42
GM 37 Modifier procedures 99
GN 37 Modifier scaling 43
GO 36 Modifier tick time 5
GP 32, 38 Modifiers 10, 42, 43, 44, 61
GQ 32, 38
GSUM 21, 38 NCOSINES 37
Normalizing 38
high-pass filter 55 Numbering Processing elements 9
I/O path 14 One pole 52, 54
INACTIVE 22, 45 One zero 52, 55
Initialization 37, 44, 47, 62 oscillator 10, 19
INITIALIZE 71 Oscillator modes 29, 30, 31
INT_MIXING 46
Integer mixing 46 pass 2, 7, 73, 76, 78
Integer notation 46 pass period 8
Interrupt 14 pipelining 75, 76
Invoke delay unit 50 processing degradation 8
INVOKE_DELAY_UNIT 50 processing element 2
Processing element arrays 72
last pass 16 processing elements 10, 11, 77
Page 107
LRNSAM Index DRAFT
Processing speed 7, 8, 9 SUM_MEMORY 24, 33, 38, 44
Processing ticks 2, 3, 4, 8 SUM_OF_COSINES 30, 31, 37
pulse train 31 SWEEP 24, 27
PULSE_TRAIN 30, 31 SWEEP scaling 40
RATE 27, 32, 36, 38 table lookup 71
Read path 14 table lookup memory 10
Read terms 43 Table lookup mode 61, 93
READ_DATA 24 TABLE_LOOKUP 62
Real time 7, 8 TERM_0 42, 48
refreshing 27 TERM_1 42, 46
RELATIVE 68 this pass 16
REPLACE_SUM_MEMORY 44 Threshold 50
Reserved Words 101, 102, 103, 104 tick 2, 78
Resonant filter scaling 55 Tick counters 73
resonant frequency 55 Tick time requirements 5, 6
resonator 55 Time division multiplexing 77, 78
Ring modulation 48 TR_U_NOISE 49
ROUND_TABLE_LOOKUP 62 Trigger mode 24
Running terms 42 Triggered uniform noise 49
Two poles 53, 55
SAIL 20, 64 Two poles COEFF0 variable 53
SAIL examples 83, 84, 85, 86, 87, 88, Two poles COEFF1 variable 53
89, 90, 91, 92, 93, 94, 95 two quadrant multiply 48
sample 1, 2 Two zeros 53
sample period 8 Two zeros COEFF0 variable 54
sampling rate 1 Two zeros COEFF1 variable 54
SAWTOOTH 29, 31
SCALE 37, 46 U_NOISE 48
Scaling terms 43 Uniform noise 48
SET_CHANNEL 66 Update ticks 2, 3, 4, 8
SET_FIELD 69 update_tick 73
SET_MODE 68
SET_OUTPUT 24, 66 Write path 14
SET_PROCEDURE 67 WRITE_DAC 36, 37, 66
Signum 47 WRITE_DATA 24, 37
Size of command buffer 71 Writing your own procedures 94
source file 64
Speed of processing 7 x_RUNNING 23
SQUARE 30, 31
Sticky mode 23, 32 ZERO_CROSSING_PULSER 47
Stream processing 14 Zero-crossing pulser 47
sum memory 2, 10, 11
Page 108